一篇文章说清楚,如果使用zookeeper实现分布式锁

1. 搭建zookeeper服务

本篇文章篇幅有限,只搭建一个单机的zookeeper测试用即可,采用docker-compose的方式

bash 复制代码
version: '3.2'
services:
  zoo1:
    image: zookeeper:3.9.2
    restart: always
    container_name: zookeeper
    ports:
      - "2181:2181"
      - "8080:8080"
    volumes:
      - ./data:/data
      - ./datalog:/datalog
      - /etc/localtime:/etc/localtime

另附集群的docker-compose yml

yaml 复制代码
version: '3.2'
 
services:
  zk1:
    image: zookeeper:latest
    hostname: zk1
    container_name: zk1
    ports:
      - "2181:2181"
      - "8081:8080"
    environment:
      ZOO_MY_ID: 1
      ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181 server.2=zk2:2888:3888;2181 server.3=zk3:2888:3888;2181
    volumes:
      - ./zk1/data:/data
      - ./zk1/datalog:/datalog
      - /etc/localtime:/etc/localtime
    networks:
      - zk-net
 
  zk2:
    image: zookeeper:latest
    hostname: zk2
    container_name: zk2
    ports:
      - "2182:2181"
      - "8082:8080"
    environment:
      ZOO_MY_ID: 2
      ZOO_SERVERS: server.1=zk1:2888:3888;2181 server.2=0.0.0.0:2888:3888;2181 server.3=zk3:2888:3888;2181
    volumes:
      - ./zk2/data:/data
      - ./zk2/datalog:/datalog
      - /etc/localtime:/etc/localtime
    networks:
      - zk-net
 
  zk3:
    image: zookeeper:latest
    hostname: zk3
    container_name: zk3
    ports:
      - "2183:2181"
      - "8083:8080"
    environment:
      ZOO_MY_ID: 3
      ZOO_SERVERS: server.1=zk1:2888:3888;2181 server.2=zk2:2888:3888;2181 server.3=0.0.0.0:2888:3888;2181
    volumes:
      - ./zk3/data:/data
      - ./zk3/datalog:/datalog
      - /etc/localtime:/etc/localtime
    networks:
      - zk-net
 
# 给集群创建一个网络,名称自己随便定义,这里取名为 zk-net
networks:
  zk-net:
    driver: bridge

2. 引入zookeeper

maven pom中增加

xml 复制代码
<dependency>
  <groupId>org.apache.zookeeper</groupId>
  <artifactId>zookeeper</artifactId>
  <version>3.9.2</version>
</dependency>

3. 修改spring 配置

基于spring boot做测试配置

yaml 复制代码
spring:
  zookeeper:
    connect-string: 172.16.5.122:2181
    session-timeout: 3000
​

4. 实现建立临时顺序节点的代码

java 复制代码
package xyz.zhiweicoding.bike;
​
import jakarta.annotation.PostConstruct;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
​
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
​
/**
 * @author zhiweicoding.xyz
 * @date 22/10/2024
 * @email diaozhiwei2k@gmail.com
 */
@Component
public class DistributedLock {
​
    private ZooKeeper zooKeeper;
    private String lockRoot = "/locks";
    private String lockNode;
    private String currentLock;
    private CountDownLatch latch = new CountDownLatch(1);
​
    @Value("${spring.zookeeper.connect-string}")
    private String connectString;
​
    @Value("${spring.zookeeper.session-timeout}")
    private int sessionTimeout;
​
    @PostConstruct
    public void init() throws IOException, InterruptedException, KeeperException {
        zooKeeper = new ZooKeeper(connectString, sessionTimeout, event -> {
            if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                latch.countDown();
            }
        });
        latch.await();
        Stat stat = zooKeeper.exists(lockRoot, false);
        if (stat == null) {
            zooKeeper.create(lockRoot, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    }
​
    public void lock() throws KeeperException, InterruptedException {
        // 创建一个临时顺序节点
        lockNode = zooKeeper.create(lockRoot + "/lock_", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        attemptLock();
    }
​
    private void attemptLock() throws KeeperException, InterruptedException {
        List<String> nodes = zooKeeper.getChildren(lockRoot, false);
        Collections.sort(nodes);
        String thisNode = lockNode.substring(lockNode.lastIndexOf('/') + 1);
​
        if (thisNode.equals(nodes.get(0))) {
            System.out.println("获得锁: " + thisNode);
            this.currentLock = thisNode;
        } else {
            String previousNode = null;
            for (int i = nodes.size() - 1; i >= 0; i--) {
                if (nodes.get(i).compareTo(thisNode) < 0) {
                    previousNode = nodes.get(i);
                    break;
                }
            }
            if (previousNode != null) {
                final String watchNode = previousNode;
                final CountDownLatch latch = new CountDownLatch(1);
                Stat stat = zooKeeper.exists(lockRoot + "/" + watchNode, event -> {
                    if (event.getType() == Watcher.Event.EventType.NodeDeleted) {
                        latch.countDown();
                    }
                });
                if (stat != null) {
                    latch.await();
                    attemptLock();
                }
            }
        }
    }
​
    public void unlock() throws KeeperException, InterruptedException {
        if (currentLock != null) {
            System.out.println("释放锁: " + currentLock);
            zooKeeper.delete(lockRoot + "/" + currentLock, -1);
            currentLock = null;
        }
    }
}

5. 使用lock

kotlin 复制代码
package xyz.zhiweicoding.bike;
​
import org.apache.zookeeper.KeeperException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
​
/**
 * @author zhiweicoding.xyz
 * @date 22/10/2024
 * @email diaozhiwei2k@gmail.com
 */
@Component
public class BusinessService {
​
    @Autowired
    private DistributedLock distributedLock;
​
    @Scheduled(fixedRate = 10000)
    public void doCriticalTask() {
        try {
            distributedLock.lock();
            // 执行临界区任务
            System.out.println("正在处理临界区任务...");
            Thread.sleep(5000);
        } catch (KeeperException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                distributedLock.unlock();
            } catch (KeeperException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
​

具体的工作流程:

  • 获取锁 :调用 distributedLock.lock() 方法,客户端尝试在 /locks 目录下创建一个临时顺序节点。

    • 如果是最小的节点(代表当前有优先权),则获取锁。
    • 否则会监听前一个节点的删除事件,当它被删除时重新尝试获取锁。
  • 执行任务 :在成功获取到锁后,执行共享资源的访问(例如模拟的 Thread.sleep() 操作)。

  • 释放锁 :任务执行完成后,通过 distributedLock.unlock() 释放锁,删除临时节点,让其他客户端有机会获取锁。

相关推荐
uzong3 小时前
技术故障复盘模版
后端
GetcharZp4 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程4 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研4 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi5 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国6 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy6 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack6 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9657 小时前
pip install 已经不再安全
后端
寻月隐君7 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github