基于zookeeper实现分布式锁&附带详细测试案例

▌前提摘要

    本文基于zokkeper集群&redis数据库

           -- zk1   192.168.0.211:2181

           -- zk1   192.168.0.211:2181

           -- zk1   192.168.0.211:2181

           -- redis 192.168.1.211:6379

 

zookeeper集群搭建:https://blog.csdn.net/qq_37936542/article/details/107096985

redis安装:https://blog.csdn.net/qq_37936542/article/details/78522728

springboot集成redis:https://blog.csdn.net/qq_37936542/article/details/80104308

 

项目主要依赖


	org.springframework.boot
	spring-boot-starter-data-redis



	redis.clients
	jedis



	com.101tec
	zkclient
	0.10

 

▌ zookeeper实现分布式锁流程图

基于zookeeper实现分布式锁&附带详细测试案例_第1张图片

1:在/locks节点下创建临时序列节点

2:调用getChildren(“l/ocks”)来获取locks下面的所有子节点

3:如果该节点的序列号是所有子节点中最小,,那么就认为客户端获取到锁,之后执行相应的业务逻辑,业务执行完毕后,删除临时节点释放锁

4:反之需该节点需要去持续监听上一节点的删除事件,直到事件被触发,程序重新回到第二步逻辑

 

▌ 代码实现

InterfaceLock:定义获取锁&释放锁的方法接口

public interface InterfaceLock {

	// 获取锁
	public void attemptLock();

	// 释放锁
	public void unLock();

}

AbstractLock:抽象一个父类,规定好抽象方法以及定义需要使用的变量

import java.util.concurrent.CountDownLatch;
import org.I0Itec.zkclient.ZkClient;

public abstract class AbstractLock implements InterfaceLock {

	// zookeeper集群地址
	protected String zkServers = "192.168.0.211:2181,192.168.0.212:2181,192.168.0.213:2181";

	// 连接zookeeper
	protected ZkClient zkClient = new ZkClient(zkServers);

	// 定义父节点
	protected String rootPath = "/locks";

	// 定义锁节点(临时序列节点)的前缀
	protected String lockPrefix = "lock_";

	// 计数器,目的让线程等待,实现持续监听
	protected CountDownLatch countDownLatch;

	// 定义所节点变量
	protected String lockPath;

	/**
	 * 获取锁包括两个阶段方法 1:创建临时序列化节点 2:尝试获取锁
	 */
	@Override
	public void attemptLock() {
		// 创建锁节点
		createLock();

		// 尝试获取锁
		tryLock();

	}

	// 创建锁节点的抽象方法
	abstract void createLock();

	// 尝试获取锁的抽象方法
	abstract void tryLock();

	/**
	 * 解锁:关闭客户端后临时节点自动删除
	 */
	@Override
	public void unLock() {
		System.out.println(Thread.currentThread().getName() + "释放锁");
		if (zkClient != null)
			zkClient.close();
	}

}

DistributeLock:分布式锁实现类

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.I0Itec.zkclient.IZkDataListener;

public class DistributeLock extends AbstractLock {

	@Override
	void createLock() {

		// 如果父节点不存在则创建
		if (!zkClient.exists(rootPath))
			zkClient.createPersistent(rootPath);

		// 创建临时序列化的锁节点
		lockPath = zkClient.createEphemeralSequential(rootPath + "/" + lockPrefix,
				Thread.currentThread().getName().getBytes());
	}

	@Override
	void tryLock() {

		// 获取所有锁节点集合
		List childs = zkClient.getChildren(rootPath);

		// 排序一波
		Collections.sort(childs);

		// 获取当前锁节点在集合里面的索引
		int index = childs.indexOf(lockPath.substring(rootPath.length() + 1));

		// 若索引最小,获取锁
		if (index == 0) {
			System.out.println(Thread.currentThread().getName() + "获得锁");
			return;
		}

		// 获取排在它前一位的锁节点
		String preLockPath = childs.get(index - 1);

		System.out.println(Thread.currentThread().getName() + "等待前锁释放");

		// 如果不存在,再去尝试获取锁
		if (!zkClient.exists(rootPath + "/" + preLockPath))
			tryLock();

		// 存在,监听前节点删除事件
		else {

			// 创建事件监听对象
			IZkDataListener iZkDataListener = new IZkDataListener() {

				public void handleDataDeleted(String path) throws Exception {
					// 监听到节点被删除,线程继续走
					if (countDownLatch != null) {
						countDownLatch.countDown();
					}
				}

				@Override
				public void handleDataChange(String dataPath, Object data) throws Exception {
					// TODO Auto-generated method stub
				}
			};

			// 注册事件监听
			zkClient.subscribeDataChanges(rootPath + "/" + preLockPath, iZkDataListener);

			// 创建CountDownLatch对象
			countDownLatch = new CountDownLatch(1);
			try {
				// 在这里让线程暂停等待,监听到前节点删除后,再往下走
				countDownLatch.await();
			} catch (Exception e) {
				e.printStackTrace();
			}

			// 获取锁
			tryLock();
		}

	}

}

至此,zookeeper实现分布式代码就已经实现了,下面模拟案例

 

▌ 案例图解

基于zookeeper实现分布式锁&附带详细测试案例_第2张图片

 

不加锁的抢购代码:SedKillController(两个服务器的代码一致)

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.mote.service.RedisService;

@RestController
public class SedKillController {

	@Autowired
	private RedisService redisService;

	private String key = "goods";

	@GetMapping("sedkill")
	public String sedKill() {

		// 获取商品数量
		Object obj = redisService.get(key);

		int mount = (int) obj;

		// 如果商品被抢完,直接返回
		if (mount < 0 || mount == 0) {
			return "很遗憾,商品已被抢完";
		}

		// 线程睡眠,目的在于放大错误
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		// 抢到商品后,将redis的商品数量减一
		redisService.set(key, --mount);

		// 打印,以便观察
		System.out.println(Thread.currentThread().getName() + ":抢到第" + (mount + 1) + "件商品");

		return "恭喜,商品抢购成功";
	}
}

启动8080、8081服务,打开浏览器请求两个服务器的接口进行测试,观察打印结果

8080打印:

8081打印:

基于zookeeper实现分布式锁&附带详细测试案例_第3张图片

完犊子,这都是写啥,下面我们使用常用的synchronized锁试试效果,修改sedkill方法

基于zookeeper实现分布式锁&附带详细测试案例_第4张图片

在重新测试,查看打印结果,不要忘记将redis的商品数据重置为10奥

8080打印:

基于zookeeper实现分布式锁&附带详细测试案例_第5张图片

 

8081打印:

基于zookeeper实现分布式锁&附带详细测试案例_第6张图片

 咦,有惊喜,比之前打印结果好看多了,至少单服务器不会出现抢购同一商品的情况了,但是还是有问题,两台服务器竟然可以抢到同一件商品,这是绝对不允许的,下面我们尝试加上刚写好的分布式锁尝试一下

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.mote.service.RedisService;
import com.mote.zk.lock.DistributeLock;

@RestController
public class SedKillController {

	@Autowired
	private RedisService redisService;

	private String key = "goods";

	@GetMapping("sedkill")
	public String sedKill() {

		// 获取分布式锁对象
		DistributeLock lock = new DistributeLock();

		// 上锁
		lock.attemptLock();

		// 获取商品数量
		Object obj = redisService.get(key);

		int mount = (int) obj;

		// 如果商品被抢完,直接返回
		if (mount < 0 || mount == 0) {
			// 解锁(不要忘了这里解锁)
			lock.unLock();
			return "很遗憾,商品已被抢完";
		}

		// 线程睡眠,目的在于放大错误

		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		// 抢到商品后,将redis的商品数量减一
		redisService.set(key, --mount);

		// 打印,以便观察
		System.out.println(Thread.currentThread().getName() + ":抢到第" + (mount + 1) + "件商品");

		// 解锁
		lock.unLock();

		return "恭喜,商品抢购成功";

	}
}

8080打印:

基于zookeeper实现分布式锁&附带详细测试案例_第7张图片

8081打印: 

基于zookeeper实现分布式锁&附带详细测试案例_第8张图片

 

tip:挺有意思的,可以去玩玩

 

你可能感兴趣的:(zookeeper)