通过召zk实现分布式锁可靠性时最高的
公平锁和可重入锁的原理
取水秩序:
(1)取水之前,先取号;
(2)号排在前面的,就可以先取水;
(3)先到的排在前面,那些后到的,一个一个挨着,在井边排成一队。
公平锁
这种排队取水模型,就是一种锁的模型。
什么是可重入锁呢?
可重入锁
Zookeeper 的节点 Znode 有四种类型
持久节点:默认的节点类型。创建节点的客户端与 zookeeper 断开连接后,该节点依旧存在。
持久节点顺序节点:所谓顺序节点,在创建节点时,Zookeeper根据创建的时间顺序给该节点 名称进行编号,持久节点顺序节点就是有顺序的持久节点。
临时节点:和持久节点相反,当创建节点的客户端与 zookeeper 断开连接后,临时节点会被删除。
临时顺序节点:有顺序的临时节点。
创建临时顺序节点:create -e -s /test 123
注意:
-e:临时节点
-s:顺序节点
创建临时顺序节点/test
ZK分布式锁的实现原理
当第一个客户端请求过来时,Zookeeper 客户端会创建一个持久节 点 locks。如果它(Client1)想获得锁,需要在 locks 节点下创建 一个顺序节点 lock1。
接着,客户端 Client1 会遍历查找 locks 下面的所有临时顺序子节点,判断自己的节点 lock1 是不是排序最小的那一个,如果是,则成功获 得锁。
这时候如果又来一个客户端 client2 前来尝试获得锁,它会在 locks 下再创建一个临时节点 lock2。看看是否排在最小的那个
客户端 client2 一样也会查找 locks 下面的所有临时顺序子节点,判 断自己的节点 lock2 是不是最小的,此时,发现 lock1 才是最小 的,于是获取锁失败。获取锁失败,它是不会甘心的,client2 向它排序靠前的节点 lock1 注册 Watcher 事件,用来监听 lock1 是否存在,也就是说client2抢锁失败进入等待状态。
此时,如果再来一个客户端Client3来尝试获取锁,它会在 locks 下 再创建一个临时节点 lock3。
同样的,client3 一样也会查找 locks 下面的所有临时顺序子节点, 判断自己的节点 lock3 是不是最小的,发现自己不是最小的,就获 取锁失败。它也是不会甘心的,它会向在它前面的节点 lock2 注册 Watcher 事件,以监听 lock2 节点是否存在。
释放锁
如果是任务完成,Client1 会显式调用删除 lock1 的指令。
如果是客户端故障了,根据临时节点得特性,lock1 是会自动删除 的。
lock1 节点被删除后,Client2 可开心了,因为它一直监听着 lock1。lock1 节点删除,Client2立刻收到通知,也会查找locks下面的所有临时顺序子节点,发下 lock2 是最小,就获得锁。
同理,Client2 获得锁之后,Client3 也对它虎视眈眈:
Zookeeper 设计定位就是分布式协调,简单易用。如果获取不到锁,只需添加一个监听器即可,很适合做分布式锁。
Zookeeper 作为分布式锁也缺点:如果有很多的客户端频繁的申请加锁、释放锁,对于 Zookeeper 集群的压力会比较大。
分布式锁解决方案_基于Zookeeper实现分布式锁
简介:
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 > 数据库