赞
踩
在分布式场景中,采用传统的锁并不能解决跨进程并发的问题,所以需要引入一个分布式锁,来解决多个节点之间的访问控制
一、Zookeeper如何解决分布式锁
基于Zookeeper的两种特性来实现分布式锁:
二、使用唯一节点特性实现分布式锁
多个应用程序去抢占锁资源时,只需要在指定节点上创建一个 /Lock 节点,由于Zookeeper中节点的唯一性特性,使得只会有一个用户成功创建 /Lock 节点,剩下没有创建成功的用户表示竞争锁失败。
问题:惊群效应
“惊群效应”,简单来说就是如果存在许多的客户端在等待获取锁,当成功获取到锁的进程释放该节点后,所有处于等待状态的客户端都会被唤醒(等待的方式自然是使用Watcher机制来监听/lock节点的删除事件),这个时候zookeeper在短时间内发送大量子节点变更事件给所有待获取锁的客户端,然后实际情况是只会有一个客户端获得锁。如果在集群规模比较大的情况下,会对zookeeper服务器的性能产生比较的影响。
三:使用有序节点实现分布式锁
因此为了解决惊群效应,可以采用Zookeeper的有序节点特性来实现分布式锁。
每个客户端都往指定的节点下注册一个临时有序节点,越早创建的节点,节点的顺序编
号就越小,那么我们可以判断子节点中最小的节点设置为获得锁。如果自己的节点不是所有子节点中最小的,意味着还没有获得锁。
不同于第一种方式性在于,每个节点只需要监听比自己小的前一个节点,当比自己小的节点删除以后,客户端会收到watcher事件,此时再次判断自己的节点是不是所有子节点中最小的,如果是则获得锁,否则就不断重复这个过程,这样就不会导致羊群效应,因为每个客户端只需要监控前一个节点。
有序节点实现分布式锁的流程
四:Curator实现分布式锁
curator对于锁这块做了一些封装,curator提供了InterProcessMutex 这样一个api。
具体的使用方法如下:
1、引入pom
<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>
2、CuratorConfig
@Configuration public class CuratorConfig { @Bean public CuratorFramework curatorFramework(){ CuratorFramework curatorFramework=CuratorFrameworkFactory .builder() .connectString("127.0.0.1:2181") .sessionTimeoutMs(15000) .connectionTimeoutMs(20000) .retryPolicy(new ExponentialBackoffRetry(1000,10)) .build(); curatorFramework.start(); return curatorFramework; } }
3、 Controller,使用锁机制
@Scope(scopeName = "prototype") @RestController @RequestMapping("/goods-stock") public class GoodsStockController { @Autowired IGoodsStockService goodsStockService; @Autowired CuratorFramework curatorFramework; @GetMapping("{goodsNo}") public String purchase(@PathVariable("goodsNo")Integer goodsNo) throws Exception { QueryWrapper<GoodsStock> queryWrapper=new QueryWrapper<>(); queryWrapper.eq("goods_no",goodsNo); InterProcessMutex lock=new InterProcessMutex(curatorFramework,"/Lock"); try { lock.acquire(); //抢占锁(阻塞) GoodsStock goodsStock=goodsStockService.getOne(queryWrapper); Thread.sleep(new Random().nextInt(1000)); if(goodsStock==null){ return "指定商品不存在"; } if(goodsStock.getStock().intValue()<1){ return "库存不够"; } goodsStock.setStock(goodsStock.getStock() - 1); boolean res = goodsStockService.updateById(goodsStock); if (res) { return "抢购书籍:" + goodsNo + "成功"; } return "抢购失败"; }finally { lock.release(); //释放锁 } } }
注:前面已经理解的Zookeeper实现分布式锁的原理,以及基于Curator完成了分布式锁的使用, Curator就是基于代码实现了这一过程。
总结:
有了 zookeeper 的一致性文件系统,锁的问题变得容易。锁服务可以分为两类:
一类是保持独占,另一个是控制时序 对于第一类,我们将 zookeeper 上的一个 znode 看作是一把锁,通过 createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除掉自己创建的 distribute_lock 节点就释放出锁。
对于第二类, /distribute_lock 已经预先存在,所有客户端在它下面创建临时顺序编号目录节点,和选 master 一样,编号最小的获得锁,用完删除,依次方便。
五、Zookeeper 队列管理(文件系统、通知机制)
两种类型的队列:
1、同步队列,当一个队列的成员都聚齐时,这个队列才可用,否则一直等待所有成员到达。
2、队列按照 FIFO 方式进行入队和出队操作。
第一类,在约定目录下创建临时目录节点,监听节点数目是否是我们要求的数目。
第二类,和分布式锁服务中的控制时序场景基本原理一致,入列有编号,出列按编号。在特定的目录下创建 PERSISTENT_SEQUENTIAL 节点,创建成功时Watcher 通知等待的队列,队列删除序列号最小的节点用以消费。此场景下Zookeeper 的 znode 用于消息存储,znode 存储的数据就是消息队列中的消息内容,SEQUENTIAL 序列号就是消息的编号,按序取出即可。由于创建的节点是持久化的,所以不必担心队列消息的丢失问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。