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()
释放锁,删除临时节点,让其他客户端有机会获取锁。