当前位置:   article > 正文

Zookeeper 分布式锁、队列_zk实现分布式队列

zk实现分布式队列

Zookeeper 分布式锁、队列

在分布式场景中,采用传统的锁并不能解决跨进程并发的问题,所以需要引入一个分布式锁,来解决多个节点之间的访问控制

一、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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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;
}
}
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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(); //释放锁
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

注:前面已经理解的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 序列号就是消息的编号,按序取出即可。由于创建的节点是持久化的,所以不必担心队列消息的丢失问题。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/593411
推荐阅读
相关标签
  

闽ICP备14008679号