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

相关推荐
why1515 小时前
腾讯(QQ浏览器)后端开发
开发语言·后端·golang
浪裡遊5 小时前
跨域问题(Cross-Origin Problem)
linux·前端·vue.js·后端·https·sprint
声声codeGrandMaster5 小时前
django之优化分页功能(利用参数共存及封装来实现)
数据库·后端·python·django
呼Lu噜6 小时前
WPF-遵循MVVM框架创建图表的显示【保姆级】
前端·后端·wpf
bing_1586 小时前
为什么选择 Spring Boot? 它是如何简化单个微服务的创建、配置和部署的?
spring boot·后端·微服务
学c真好玩6 小时前
Django创建的应用目录详细解释以及如何操作数据库自动创建表
后端·python·django
Asthenia04126 小时前
GenericObjectPool——重用你的对象
后端
Piper蛋窝7 小时前
Go 1.18 相比 Go 1.17 有哪些值得注意的改动?
后端
excel7 小时前
招幕技术人员
前端·javascript·后端
盖世英雄酱581367 小时前
什么是MCP
后端·程序员