ZooKeeper 实现一个分布式锁 (java代码)

59 阅读4分钟

以下是基于 Java 语言(使用 Apache Curator 框架,ZooKeeper 官方推荐的客户端,已封装分布式锁核心逻辑)的分布式锁 Demo,包含 加锁、执行业务、释放锁 完整流程,代码简洁且符合生产级规范。

一、前置依赖

需在 Maven 项目的 pom.xml 中引入 Curator 依赖(Curator 已封装 ZooKeeper 原生 API 的复杂逻辑,如连接管理、Watcher 监听、重试机制):

<dependencies>
<!--// Zookeeper Curator client-->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.5.0</version>
</dependency>

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>5.5.0</version>
</dependency>
<!--Zookeeper client-->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.7.1</version>
</dependency>

</dependencies>

二、ZooKeeper 分布式锁

核心逻辑:通过 Curator 的 InterProcessMutex(基于临时顺序节点实现的可重入分布式锁),模拟多个线程(对应分布式场景下的多个服务节点)竞争锁,确保同一时间仅一个线程执行“临界区业务”。

public class zookeeperTest {
    //测试zookeeper的分布式锁
    private static final String ZK_ADDRESS = "localhost:2181";
    //分布式锁路径
    private static final String LOCK_PATH = "/my_lock";
    //模拟的线程数量
    private static final int THREAD_COUNT = 5;

    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        //循环创建线程,模拟并发抢锁
        for (int i = 0; i < THREAD_COUNT; i++) {
            final int threadId = i;
            new Thread(new Runnable() {
                public void run() {
                    CuratorFramework zkClient = null;
                    InterProcessMutex zkLock = null;
                    try {
                        zkClient = CuratorFrameworkFactory.builder().connectString(ZK_ADDRESS)
                                //设置会话超时时间和连接超时时间
                                .sessionTimeoutMs(5000)
                                .connectionTimeoutMs(3000)
                                //设置重试策略
                                .retryPolicy(new ExponentialBackoffRetry(1000, 3)).build();

                        //启动客户端
                        zkClient.start();
                        //创建分布式锁
                        zkLock = new InterProcessMutex(zkClient, LOCK_PATH);
                        //尝试获取锁,最多等待10秒
                        boolean isLockAcquire = zkLock.acquire(10, TimeUnit.SECONDS);
                        if (isLockAcquire) {
                            System.out.println("Thread " + threadId + " 成功获取分布式锁,执行临界区业务");
                            //模拟业务处理
                            Thread.sleep(2000);
                            System.out.println("Thread " + threadId + " 业务处理完成,释放锁");
                        } else {
                            //获取锁失败,处理相应逻辑
                            System.out.println("Thread " + threadId + " 没有获取到锁.");
                        }
                    } catch (Exception e) {
                        System.out.println("Thread " + threadId + " 处理异常: " + e.getMessage());
                        e.printStackTrace();
                    } finally {
                        //释放锁和关闭客户端
                        if (zkLock != null && zkLock.isAcquiredInThisProcess()) {
                            try {
                                zkLock.release();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                        if (zkClient != null) {
                            zkClient.close();
                        }
                        //确保latch递减,防止主线程一直等待
                        latch.countDown();
                    }
                }
            }).start();
        }
        try {
            latch.await();
            System.out.println("所有线程处理完成,主线程继续执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

三、代码核心说明

1.Curator 客户端初始化:

  • ExponentialBackoffRetry:失败重试策略,避免因网络波动导致连接失败,生产环境建议配置合理的重试次数和等待时间。

  • 客户端需调用 start() 启动,close() 关闭,避免资源泄漏。

2.分布式锁核心类 InterProcessMutex:

  • 底层基于 ZooKeeper 临时顺序节点 和 Watcher 监听 实现,无需手动处理节点创建、排序、监听逻辑。

  • 支持 可重入(同一线程多次 acquire() 不会死锁)和 超时等待(acquire(timeout, unit) 避免无限阻塞)。

3.锁释放逻辑:

  • 必须在 finally 中调用 release(),确保无论业务成功/失败,锁都能释放。

  • 释放前需通过 isAcquiredInThisProcess() 判断当前线程是否持有锁,避免误释放其他线程的锁。

四、运行前提与结果

1.运行前提:

  • 已启动 ZooKeeper 集群(单机也可,地址改为 127.0.0.1:2181),且集群状态正常。

  • 确保程序有权限操作 ZooKeeper 节点(默认无权限控制,生产环境需配置 ACL)。

2.预期运行结果:

5个线程会按“先请求先获取”的公平顺序抢锁,同一时间仅 1 个线程执行业务,执行完释放锁后,下一个线程才能获取锁,示例输出如下:

Thread 3 成功获取分布式锁,执行临界区业务
Thread 3 业务处理完成,释放锁
Thread 2 成功获取分布式锁,执行临界区业务
Thread 2 业务处理完成,释放锁
Thread 4 成功获取分布式锁,执行临界区业务
Thread 4 业务处理完成,释放锁
Thread 0 成功获取分布式锁,执行临界区业务
Thread 0 业务处理完成,释放锁
Thread 1 成功获取分布式锁,执行临界区业务
Thread 1 业务处理完成,释放锁
所有线程处理完成,主线程继续执行

五、生产环境注意事项

  • 锁路径设计:建议按“业务类型+资源标识”设计锁路径(如 /distribute-lock/order/1001,1001 为订单ID),避免全局锁导致性能瓶颈。

  • 会话超时配置:sessionTimeoutMs 需大于业务最大执行时间,避免客户端会话超时导致锁被自动释放(临时节点随会话消失)。

  • 集群高可用:ZooKeeper 集群需至少 3 个节点,确保宕机 1 个节点后仍能正常提供服务,避免锁服务不可用。