环境准备
zookeeper准备
首先你需要一个zookeeper服务器,或者是一个zookeeper集群。我已经准备好了一个zookeeper集群,如图:
当然一个单节点的zookeeper也可以搭建分布式锁。如果你还没有zookeeper,那么你可以参考我写的搭建zookeeper集群的文章:https://blog.csdn.net/m0_51510236/article/details/132834141
SpringBoot项目准备
这个步骤比较简单,我的代码已经提交至公共代码仓库,代码仓库地址为:https://gitcode.net/m0_51510236/zookeeper-distribution-lock
代码编写
pom.xml依赖文件
首先我们需要添加一下SpringCloudZookeeper的依赖,因为是只使用到了SpringCloud的zookeeper模块,所以没必要引入整个SpringCloud,如果你已经引入了整个SpringCloud,那么就没必要引入该模块了,如图:
其次我们要在dependency里面引入zookeeper的依赖:
<!-- Zookeeper 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper</artifactId>
</dependency>
<!-- Zookeeper 分布式锁 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
然后我们还需要引入SpringBoot测试模块的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
整体pom.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.15</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>city.yueyang</groupId>
<artifactId>zookeeper-distribution-lock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zookeeper-distribution-lock</name>
<description>zookeeper分布式锁</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Zookeeper 客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper</artifactId>
</dependency>
<!-- Zookeeper 分布式锁 -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-zookeeper-dependencies</artifactId>
<version>3.1.4</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yaml文件
application.yaml是SpringCloud的配置文件,我们只需要配置zookeeper的地址即可,文件内容为:
spring:
cloud:
zookeeper:
# 可以只写一个,如果是有多个节点的zookeeper集群,那么多个节点用英文逗号隔开
connect-string: 192.168.1.181:2181,192.168.1.182:2181,192.168.1.183:2181
Runnable类
我们需要建立一个线程池来模拟多线程整合zookeeper做分布式锁,那么我们需要一个Runnable类来定义多线程的任务,java代码为:
package city.yueyang.lock.thread;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CountDownLatch;
/**
* <p></p>
*
* @author XiaoHH
* @version 1.0.0
* @date 2023-09-13 星期三 20:44:59
* @file DistributionLockRunnable.java
*/
public class DistributionLockRunnable implements Runnable {
private static final Logger log = LoggerFactory.getLogger(DistributionLockRunnable.class);
/**
* 分布式锁对象
*/
private final InterProcessMutex lock;
/**
* CountDownLatch锁,避免多线程没有执行完就退出程序
*/
private final CountDownLatch countDownLatch;
public DistributionLockRunnable(InterProcessMutex lock, CountDownLatch countDownLatch) {
this.lock = lock;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
log.info("线程名:{},尝试获取锁", threadName);
// 获取锁
lock.acquire();
log.info("线程名:{},获取锁成功,正在处理数据", threadName);
Thread.sleep(1000); // 模拟处理数据睡眠一秒钟
log.info("线程名:{},数据处理成功", threadName);
} catch (Throwable e) {
log.error("线程名:{},发生了错误", threadName, e);
} finally {
try {
// 退出锁
lock.release();
log.info("线程名:{},锁已释放", threadName);
// 锁-1
countDownLatch.countDown();
} catch (Exception e) {
log.error("线程名:{},释放锁的时候发生了错误", threadName, e);
}
}
}
}
注意下面两行代码:
lock.acquire(); // 获取锁
lock.release(); // 释放锁
代码里面日志打印已经很详细,这里就不再过多解释
测试类编写
我们编写一个测试用例来测试这个分布式锁:
package city.yueyang.lock;
import city.yueyang.lock.thread.DistributionLockRunnable;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.*;
@SpringBootTest
public class ZookeeperDistributionLockApplicationTests {
/**
* 日志记录对象
*/
private static final Logger log = LoggerFactory.getLogger(ZookeeperDistributionLockApplicationTests.class);
/**
* zookeeper的客户端
*/
@Autowired
private CuratorFramework curatorFramework;
/**
* zookeeper锁的路径,这也会被认为是zookeeper锁的标识
*/
private static final String LOCK_PATH = "/lock/test-zookeeper-lock";
@Test
public void testDistributionLock() {
// 处理十个任务
final int taskAmount = 10;
// 使用 CountDownLatch 锁避免多线程没有执行完程序就停止
CountDownLatch countDownLatch = new CountDownLatch(taskAmount);
// 创建zookeeper的锁对象,如果第二个参数也就是path路径是一样的,那么就会被认为是同一把锁
InterProcessMutex lock = new InterProcessMutex(this.curatorFramework, LOCK_PATH);
// 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(taskAmount, taskAmount, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(taskAmount));
for (int i = 0; i < taskAmount; i++) {
// 创建一个任务
Runnable task = new DistributionLockRunnable(lock, countDownLatch);
// 使用线程池去执行它
executor.execute(task);
}
try {
// 等待所有的countDown锁执行完毕本线程结束
countDownLatch.await();
} catch (InterruptedException e) {
log.error("countdown锁被终止了");
} finally {
// 终止线程池
executor.shutdown();
}
}
}
创建锁对象的代码主要是下面这行:
InterProcessMutex lock = new InterProcessMutex(this.curatorFramework, "锁路径的字符串");
重点是第二个参数,是锁路径。无论new出多少个锁对象,只要锁路径的字符串是一样的,就会被zookeeper认为是同一把锁
测试代码
我们直接运行这个测试用例,可以发现都是有序的获取锁,并没有并发执行的问题,这也证明了分布式锁搭建成功:
代码已提交到公共代码仓库。代码仓库地址:https://gitcode.net/m0_51510236/zookeeper-distribution-lock