zookeeper集群使用zap协议来保证集群之间数据的一致性,频繁的进行写、监听操作将对zk集群产生较大压力,所以不推荐大家使用。
推荐使用redis作为分布式锁:
redisson是redis的一个组件,是redis提供的一种分布锁的实现,它实现的jdk lock锁的接口,还有就是其底层是lua脚本,将锁的获取与锁的设置超时时间实现原子性操作,避免获取到锁而服务器宕机超时时间设置失败的情况。
参考我之前的文章 redis使用redisson作为客户端配合springboot作为分布式锁的案例
但是作为一个热爱学习的人,依然选择把我总结的zookeeper实现分布式锁的流程分享给大家
有序节点:顾名思义就是有顺序的节点。zk会在生成节点时根据现有的节点数量添加整数序号。比如已经存在节点/lock/node-0000000000,下一个节点就是/lock/node-0000000001。
临时节点:临时节点只在zk会话期间存在,会话结束或超时时会被zk自动删除。
事件监听:通过zk的事件监听机制可以让客户端收到节点状态变化。主要的事件类型有节点数据变化、节点的删除和创建。
PERSISTENT:持久化的节点。一旦创建后,即使客户端与zk断开了连接,该节点依然存在。
PERSISTENT_SEQUENTIAL:持久化顺序编号节点。比PERSISTENT节点多了节点自动按照顺序编号。
EPHEMERAL:临时节点。当客户端与zk断开连接之后,该节点就被删除。
EPHEMERAL_SEQUENTIAL:临时顺序编号节点。比EPHEMERAL节点多了节点自动按照顺序编号。(分布式锁实现使用该节点类型)
算法流程如下:
1、每个客户端创建临时有序节点
2、客户端获取节点列表,判断自己是否列表中的第一个节点,如果是就获得锁,如果不是就监听自己前面的节点,等待前面节点被删除。
3、如果获取锁就进行正常的业务流程,执行完释放锁。
上述步骤2中,有人可能担心如果节点发现自己不是序列最小的节点,准备添加监听器,但是这个时候前面节点正好被删除,这时候添加监听器是永远不起作用的,其实zk的API可以保证读取和添加监听器是一个原子操作。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies>
@RestController
public class NoDistributeController {
/**
* 无分布式锁
*/
@Autowired
NoDistributeService redisDistributeService;
/**
* 查询剩余订单结果接口
* @return
*/
@GetMapping("/query")
public String query() {
return redisDistributeService.queryMap();
}
/**
* 下单接口
* @return
*/
@GetMapping("/order")
public String order() {
redisDistributeService.order();
return redisDistributeService.queryMap();
}
}
@Service
public class NoDistributeService {
/**
* 模拟商品信息表
*/
private static Map<String,Integer> products;
/**
* 模拟库存
*/
private static Map<String,Integer> stock;
/**
* 订单
*/
private static Map<String,String> orders;
static {
products = new HashMap<>();
stock = new HashMap<>();
orders = new HashMap<>();
//模拟订单表数据 订单编号 苹果 库存 100000
products.put("苹果",100000);
//模拟库存表数据 订单编号 苹果 库存100000
stock.put("苹果",100000);
}
/**
* 模拟查询秒杀成功返回的信息
* @return 返回拼接的秒杀商品结果字符串
*/
public String queryMap() {
String pid = "苹果";
return "秒杀商品限量:" + products.get(pid) + "份,还剩:"+stock.get(pid) +"份,成功下单:"+orders.size() + "人";
}
/**
* 下单
*/
public void order() {
String pid = "苹果";
//从库存表中获取库存余量
int stockNum = stock.get(pid);
//如果库存为0 则输出库存不足
if(stockNum == 0) {
System.out.println("商品库存不足");
}else{ //如果有库存
//往订单表中插入数据 生成UUID作为用户ID pid
orders.put(UUID.randomUUID().toString(),pid);
//线程休眠 模拟其他操作
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//减库存操作
stock.put(pid,stockNum-1);
}
}
}
2秒,下单1000次
查看结果:http://localhost:8080/query
可以看出, 下单了994个人,而订单只下了29份,严重出现问题。
//需要使用分布式锁的地方,代码如下:
String lockOn= "锁的名称";
//curatorFramework cutator客户端
InterProcessMutex mutex = new InterProcessMutex(curatorFramework,lockOn);
//入参传入超时时间、单位,抢夺时,如果出现堵塞,会在超过该时间后,返回false
boolean locked =mutex.acquire(2,TimeUnit.SECONDS);
//finally部分 mutex.release();
修改service
@Service
public class NoDistributeService {
/**
* 模拟商品信息表
*/
private static Map<String,Integer> products;
/**
* 模拟库存
*/
private static Map<String,Integer> stock;
/**
* 订单
*/
private static Map<String,String> orders;
static {
products = new HashMap<>();
stock = new HashMap<>();
orders = new HashMap<>();
//模拟订单表数据 订单编号 苹果 库存 100000
products.put("苹果",100000);
//模拟库存表数据 订单编号 苹果 库存100000
stock.put("苹果",100000);
}
/**
* 模拟查询秒杀成功返回的信息
* @return 返回拼接的秒杀商品结果字符串
*/
public String queryMap() {
String pid = "苹果";
return "秒杀商品限量:" + products.get(pid) + "份,还剩:"+stock.get(pid) +"份,成功下单:"+orders.size() + "人";
}
/**
* 下单
*/
public void order() {
//创建分布式锁, 锁空间的根节点路径为/curator/lock
InterProcessMutex mutex = new InterProcessMutex(client,"/curator/lock");
try {
boolean acquire = mutex.acquire(0, TimeUnit.SECONDS);
if(!acquire){
logger.info("锁被占用");
return;
}else {
String pid = "苹果";
//从库存表中获取库存余量
int stockNum = stock.get(pid);
//如果库存为0 则输出库存不足
if(stockNum == 0) {
System.out.println("商品库存不足");
}else{ //如果有库存
//往订单表中插入数据 生成UUID作为用户ID pid
orders.put(UUID.randomUUID().toString(),pid);
//线程休眠 模拟其他操作
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//减库存操作
stock.put(pid,stockNum-1);
}
}
} catch (Exception e) {
logger.info("锁出现异常");
} finally {
try {
mutex.release();
} catch (Exception e) {
logger.info("释放锁出现异常");
}
}
}
}
如果有疑问,可评论留言,及时解答,谢谢