![](https://img-blog.csdnimg.cn/63934af0db0d48aca849249da85d4738.png)
通过召zk实现分布式锁可靠性时最高的
公平锁和可重入锁的原理
取水秩序:
(1)取水之前,先取号;
(2)号排在前面的,就可以先取水;
(3)先到的排在前面,那些后到的,一个一个挨着,在井边排成一队。
![](https://img-blog.csdnimg.cn/e9636be74b6e4014a03d148d273ad4f0.png)
公平锁
这种排队取水模型,就是一种锁的模型。
什么是可重入锁呢?
![](https://img-blog.csdnimg.cn/73278694bd5a4a1db17cf1b6a9d6b356.png)
可重入锁
Zookeeper 的节点 Znode 有四种类型
持久节点:默认的节点类型。创建节点的客户端与 zookeeper 断开连接后,该节点依旧存在。
持久节点顺序节点:所谓顺序节点,在创建节点时,Zookeeper根据创建的时间顺序给该节点 名称进行编号,持久节点顺序节点就是有顺序的持久节点。
临时节点:和持久节点相反,当创建节点的客户端与 zookeeper 断开连接后,临时节点会被删除。
临时顺序节点:有顺序的临时节点。
创建临时顺序节点:create -e -s /test 123
注意:
-e:临时节点
-s:顺序节点
![](https://img-blog.csdnimg.cn/fe624f5e55a64c2a9cabb9678029402a.png)
创建临时顺序节点/test
![](https://img-blog.csdnimg.cn/cd99eb8bf1064b97b6db630c74a19fbb.png)
ZK分布式锁的实现原理
当第一个客户端请求过来时,Zookeeper 客户端会创建一个持久节 点 locks。如果它(Client1)想获得锁,需要在 locks 节点下创建 一个顺序节点 lock1。
![](https://img-blog.csdnimg.cn/256e3489c74f47e1a6f48c217e05407a.png)
接着,客户端 Client1 会遍历查找 locks 下面的所有临时顺序子节点,判断自己的节点 lock1 是不是排序最小的那一个,如果是,则成功获 得锁。
![](https://img-blog.csdnimg.cn/12320d01f43b4088a4c027292d18c012.png)
这时候如果又来一个客户端 client2 前来尝试获得锁,它会在 locks 下再创建一个临时节点 lock2。看看是否排在最小的那个
![](https://img-blog.csdnimg.cn/059a9a5162844ddc99d0030d956ab970.png)
客户端 client2 一样也会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock2 是不是最小的,此时,发现 lock1 才是最小 的,于是获取锁失败。获取锁失败,它是不会甘心的,client2 向它排序靠前的节点 lock1 注册 Watcher 事件,用来监听 lock1 是否存在,也就是说client2抢锁失败进入等待状态。
![](https://img-blog.csdnimg.cn/ac47720316be47508c162f79300f43bf.png)
此时,如果再来一个客户端Client3来尝试获取锁,它会在 locks 下 再创建一个临时节点 lock3。
![](https://img-blog.csdnimg.cn/4809fb6078544c2da99e7ea7ad7d8886.png)
同样的,client3 一样也会查找 locks 下面的所有临时顺序子节点, 判断自己的节点 lock3 是不是最小的,发现自己不是最小的,就获 取锁失败。它也是不会甘心的,它会向在它前面的节点 lock2 注册 Watcher 事件,以监听 lock2 节点是否存在。
![](https://img-blog.csdnimg.cn/5a78746b63f841d4b4489047627fa3cf.png)
释放锁
如果是任务完成,Client1 会显式调用删除 lock1 的指令。
![](https://img-blog.csdnimg.cn/94f551da6355433281869458d2338a6b.png)
如果是客户端故障了,根据临时节点得特性,lock1 是会自动删除 的。
lock1 节点被删除后,Client2 可开心了,因为它一直监听着 lock1。lock1 节点删除,Client2立刻收到通知,也会查找locks下面的所有临时顺序子节点,发下 lock2 是最小,就获得锁。
![](https://img-blog.csdnimg.cn/36a8c1d2f0f44d84b36e22a5a1f0cd3d.png)
同理,Client2 获得锁之后,Client3 也对它虎视眈眈:
Zookeeper 设计定位就是分布式协调,简单易用。如果获取不到锁,只需添加一个监听器即可,很适合做分布式锁。
Zookeeper 作为分布式锁也缺点:如果有很多的客户端频繁的申请加锁、释放锁,对于 Zookeeper 集群的压力会比较大。
分布式锁解决方案_基于Zookeeper实现分布式锁
![](https://img-blog.csdnimg.cn/5be2635d5b38466ea81b349196db7f99.png)
简介:
Apache Curator是一个比较完善的ZooKeeper客户端框架,通过封 装的一套高级API 简化了ZooKeeper的操作。
Curator主要解决了三类问题:
封装ZooKeeper client与ZooKeeper server之间的连接处理。
提供了⼀套Fluent风格的操作API。
提供ZooKeeper各种应用场景(比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等)的抽象封装。
Curator主要从以下几个方面降低了zk使用的复杂性
重试机制:提供可插拔的重试机制, 它将给捕获所有可恢复的异常配置⼀个重试策略,并且内部也 提供了几种标准的重试策略(比如指数补偿)。
连接状态监控:Curator初始化之后会⼀直对zk连接进⾏监听,⼀旦发现连接状态发⽣变化将会作 出相应的处理。
zk客户端实例管理:Curator会对zk客户端到server集群的连接进⾏管理,并在需要的时候重建zk 实例,保证与zk集群连接的可靠性。
各种使用场景支持:Curator实现了zk支持的大部分使⽤场景(甚至包括zk自身不支持的场景), 这些实现都遵循了zk的最佳实践,并考虑了各种极端情况。
在项目的pom.xml中引入Curator依赖
<dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-client</artifactId> <version>5.2.0</version> </dependency> |
在项目中创建包config,并创建配置类ZookeeperConfig
package com.ss.demo.config; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ZookeeperConfig { /** * 创建Curator的客户端 * @return */ @Bean public CuratorFramework zookeeperClient() { CuratorFramework client = CuratorFrameworkFactory.builder() .connectString("127.0.0.1:2181") //zk的连接地址 .sessionTimeoutMs(5000) //会话的超时时间默认为6秒 .connectionTimeoutMs(5000) //连接创建的超时时间 .retryPolicy(new ExponentialBackoffRetry(1000, 3)) //连接的重试次数【1秒重试3次】 .build(); client.start(); //启动即可 return client; } } |
修改service中的接口:ITOrderService
package com.ss.demo.service;
import com.baomidou.mybatisplus.extension.service.IService; import com.ss.demo.domain.TOrder;
/** * <p> * 服务类 * </p> * * @author laozhang * @since 2023-04-04 */ public interface ITOrderService extends IService<TOrder> { /** * 创建订单方法 * @param productId * @param count * @return */ String createOrder(Integer productId, Integer count);
/** * 使用悲观锁进行实现 * @param productId * @param count * @return */ String createOrderPessimisticlock(Integer productId, Integer count);
/** * 乐观锁 * @param productId * @param count * @return */ String createOrderOptmisticlock(Integer productId, Integer count);
/** * Redis操作 * @param productId * @param count * @return */ String createOrderRedis(Integer productId, Integer count);
/** * Redisson操作 * @param productId * @param count * @return */ String createOrderRedisson(Integer productId, Integer count);
/** * zookeeper操作 * @param productId * @param count * @return */ String createOrderZookeeper(Integer productId, Integer count); } |
修改service实现类:ITOrderServiceImpl
@Autowired private CuratorFramework curatorFramework;
/** * zookeeper操作 * @param productId * @param count * @return */ @Override public String createOrderZookeeper(Integer productId, Integer count) throws Exception { //创建锁【注这里是公平锁】 InterProcessMutex lock = new InterProcessMutex(curatorFramework, "/lockPath");
//尝试获得锁,我们在这里可以等待5秒钟 if(lock.acquire(5,TimeUnit.SECONDS)) { //第一个参数为时间,第二个参数为时间的单位 try { //根据商品id获取商品信息 Product product = productMapper.selectById(productId); if(product == null) { throw new RuntimeException("购买商品不存在"); } log.info(Thread.currentThread().getName() +"库存数量" + product.getCount()); //校验库存 if(count > product.getCount()) { throw new RuntimeException("库存不足"); } //更新库存 Integer iCount = product.getCount() - count; product.setCount(iCount); //更新操作 productMapper.updateById(product); //创建订单操作 TOrder order = new TOrder(); order.setOrderStatus(1); order.setReceiverName("张三"); order.setReceiverMobile("12345678765"); //设置订单价格【商品单价*商品数量】 order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count))); orderMapper.insert(order); //插入订单操作 //创建订单商品表的操作 OrderItem orderItem = new OrderItem(); orderItem.setOrderId(order.getId()); //订单Id orderItem.setProduceId(product.getId()); //商品Id orderItem.setPurchasePrice(product.getPrice()); //购买价格 orderItem.setPurchaseNum(count); //购买数量 orderItemMapper.insert(orderItem); return order.getId(); } catch (Exception e) { e.printStackTrace(); } finally { lock.release(); //释放锁 } } return "创建失败"; } |
修改controller:TOrderController
package com.ss.demo.controller; import com.ss.demo.service.ITOrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureOrder; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * <p> * 前端控制器 * </p> */ @RestController @RequestMapping("/order") public class TOrderController { @Autowired private ITOrderService orderService;
@PostMapping("/create") public String createOrder(Integer productId, Integer count) throws Exception { //return orderService.createOrder(productId, count); //return orderService.createOrderRedis(productId,count); //return orderService.createOrderRedisson(productId, count); return orderService.createOrderZookeeper(productId, count); } } |
启动服务9091,9090
使用Jmeter记性测试
把数据库所有数据复原
测试略过
三种分布式锁对比
数据库分布式锁实现
优点:
简单,
使用方便,
不需要引入 Redis、Zookeeper 等中间 件。
缺点:
不适合高并发的场景
db 操作性能较差
Redis 分布式锁实现
优点:
性能好,适合高并发场景
较轻量级
有较好的框架支持,如 Redisson
缺点:
过期时间不好控制
需要考虑锁被别的线程误删场景
Zookeeper 分布式锁实现
优点:
有较好的性能和可靠性
有封装较好的框架,如 Curator
缺点:
性能不如 Redis 实现的分布式锁
比较重的分布式锁。
汇总对比:
从性能角度:Redis > Zookeeper >= 数据库
从实现的复杂性角度:Zookeeper > Redis > 数据库
从可靠性角度:Zookeeper > Redis > 数据库