当前位置:首页 > Java > 正文

Java语言分布式锁实战指南(从零开始掌握Redis与ZooKeeper实现)

在现代高并发的分布式系统中,多个服务实例可能同时操作同一份数据,如果不加以控制,很容易导致数据不一致甚至系统崩溃。这时候,Java分布式锁就成为保障数据安全的关键技术。

Java语言分布式锁实战指南(从零开始掌握Redis与ZooKeeper实现) Java分布式锁 Redis实现分布式锁 ZooKeeper分布式锁 分布式系统并发控制 第1张

什么是分布式锁?

分布式锁是一种在分布式环境下协调多个进程或线程对共享资源进行互斥访问的机制。与单机环境下的synchronized或ReentrantLock不同,分布式锁需要跨多个JVM甚至多台服务器生效。

常见的应用场景包括:

  • 秒杀系统中的库存扣减
  • 订单号生成避免重复
  • 定时任务在集群中只执行一次

实现分布式锁的常见方式

目前主流的实现方式主要有两种:基于Redis基于ZooKeeper。下面我们分别介绍这两种方式的原理和代码实现。

1. 基于Redis实现分布式锁

Redis因其高性能和原子操作特性,成为实现分布式锁的首选。核心思路是利用SET命令的NX(Not eXists)和EX(Expire)参数实现“加锁+自动过期”一体化操作。

下面是一个使用Jedis客户端实现的简单分布式锁工具类:

import redis.clients.jedis.Jedis;import java.util.Collections;public class RedisDistributedLock {    private static final String LOCK_SUCCESS = "OK";    private static final String SET_IF_NOT_EXIST = "NX";    private static final String SET_WITH_EXPIRE_TIME = "PX";    private static final Long RELEASE_SUCCESS = 1L;    /**     * 尝试获取分布式锁     * @param jedis Redis客户端     * @param lockKey 锁的key     * @param requestId 请求标识(用于解锁时验证)     * @param expireTime 超时时间(毫秒)     * @return 是否获取成功     */    public static boolean tryGetDistributedLock(Jedis jedis,             String lockKey, String requestId, int expireTime) {                String result = jedis.set(lockKey, requestId,                 SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);                return LOCK_SUCCESS.equals(result);    }    /**     * 释放分布式锁     * @param jedis Redis客户端     * @param lockKey 锁的key     * @param requestId 请求标识     * @return 是否释放成功     */    public static boolean releaseDistributedLock(Jedis jedis,             String lockKey, String requestId) {                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +                        "return redis.call('del', KEYS[1]) else return 0 end";                Object result = jedis.eval(script, Collections.singletonList(lockKey),                                    Collections.singletonList(requestId));                return RELEASE_SUCCESS.equals(result);    }}

注意:这里使用Lua脚本保证了解锁操作的原子性,防止误删其他线程的锁。requestId通常使用UUID生成,确保唯一性。

2. 基于ZooKeeper实现分布式锁

ZooKeeper通过其临时顺序节点的特性天然支持分布式锁。当多个客户端尝试创建同名节点时,ZooKeeper会按顺序分配编号,最小编号的客户端获得锁。

Apache Curator框架封装了ZooKeeper的复杂操作,提供了InterProcessMutex类来简化分布式锁的使用:

import org.apache.curator.framework.CuratorFramework;import org.apache.curator.framework.CuratorFrameworkFactory;import org.apache.curator.framework.recipes.locks.InterProcessMutex;import org.apache.curator.retry.ExponentialBackoffRetry;public class ZkDistributedLockExample {    public static void main(String[] args) throws Exception {        // 创建ZooKeeper客户端        CuratorFramework client = CuratorFrameworkFactory.builder()                .connectString("localhost:2181")                .retryPolicy(new ExponentialBackoffRetry(1000, 3))                .build();        client.start();        // 创建分布式锁        InterProcessMutex lock = new InterProcessMutex(client, "/my-lock");        try {            // 获取锁(阻塞直到成功)            lock.acquire();                        // 执行业务逻辑            System.out.println("执行受保护的业务逻辑...");            Thread.sleep(2000);                    } finally {            // 释放锁            lock.release();            client.close();        }    }}

如何选择合适的实现方式?

- 如果你的系统已经使用了Redis,且对性能要求极高,可以选择Redis实现;但要注意处理锁过期、主从切换等问题。

- 如果你更看重强一致性和可靠性,且系统已集成ZooKeeper,那么ZooKeeper方案更为稳妥。

总结

本文详细介绍了Java分布式锁的基本概念、两种主流实现方式(Redis实现分布式锁ZooKeeper分布式锁),并通过完整代码示例帮助初学者快速上手。在实际项目中,合理使用分布式锁能有效解决分布式系统并发控制问题,保障数据一致性与系统稳定性。

建议在生产环境中结合业务场景、系统架构和已有中间件选型,谨慎评估后选择最适合的方案。