一篇文章说清楚,如果使用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() 释放锁,删除临时节点,让其他客户端有机会获取锁。

相关推荐
机器之心2 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
.生产的驴2 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
顽疲2 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端
机器之心3 小时前
AAAI 2025|时间序列演进也是种扩散过程?基于移动自回归的时序扩散预测模型
人工智能·后端
hanglove_lucky4 小时前
本地摄像头视频流在html中打开
前端·后端·html
皓木.5 小时前
(自用)配置文件优先级、SpringBoot原理、Maven私服
java·spring boot·后端
i7i8i9com5 小时前
java 1.8+springboot文件上传+vue3+ts+antdv
java·spring boot·后端
秋意钟5 小时前
Spring框架处理时间类型格式
java·后端·spring
我叫啥都行5 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
Stark、6 小时前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端