在分布式系统中,任何操作都可能跨 络,在部分并发场景下,如无法保证顺序性,会产生冲突。在分布式系统中,不同进程需要互斥访问共享资源是,需要引入并发控制机制-分布式锁。
基于ZooKeeper的分布式锁
ZooKeeper是一个分布式协调服务,常用于维护配置信息、注册发现服务、集群管理及分布式锁。
在ZooKeeper 集群中,节点共有如下三种角色:
为了保证高可用,ZooKeeper需要以集群形态来部署,这样只要集群中大部分机器是可用的(能够容忍一定的机器故障),那么ZooKeeper本身仍然是可用的。客户端在使用ZooKeeper时,需要知道集群机器列表,通过与集群中的某一台机器建立TCP连接来使用服务,客户端使用这个TCP链接来发送请求、获取结果、获取监听事件以及发送心跳包。如果这个连接异常断开了,客户端可以连接到另外的机器上。
客户端的读请求可以被集群中的任意一台机器处理,如果读请求在节点上注册了监听器,这个监听器也是由所连接的ZooKeeper机器来处理。对于写请求,这些请求会同时发给其他ZooKeeper 机器并且达成一致后,请求才会返回成功。因此,随着ZooKeeper 的集群机器增多,读请求的吞吐会提高但是写请求的吞吐会下降。
如何使用ZooKeeper实现分布式锁?
在描述算法流程之前,先看下ZooKeeper中几个关于节点的有趣的性质:
(1)有序节点:
假如当前有一个父节点为/lock,我们可以在这个父节点下面创建子节点;ZooKeeper提供了一个可选的有序特性,例如我们可以创建子节点“/lock/node-”并且指明有序,那么zookeeper在生成子节点时会根据当前的子节点数量自动添加整数序 ,也就是说如果是第一个创建的子节点,那么生成的子节点为/lock/node-0000000000,下一个节点则为/lock/node-0000000001,依次类推。
(2)临时节点:
客户端可以建立一个临时节点,在会话结束或者会话超时后,ZooKeeper会自动删除该节点。
(3)事件监听:
在读取数据时,我们可以同时对节点设置事件监听,当节点数据或结构变化时,ZooKeeper会通知客户端。
当前ZooKeeper有如下四种事件:1)节点创建;2)节点删除;3)节点数据修改;4)子节点变更。
ZooKeeper 实现分布式锁的思路,就是通过创建临时顺序节点来实现的。
下面描述使用ZooKeeper实现分布式锁的算法流程,假设锁空间的根节点为/lock:
1. 客户端连接ZooKeeper,并在/lock下创建临时的且有序的子节点,第一个客户端对应的子节点为/lock/node-0000000000,第二个为/lock/node-0000000001,以此类推。
2. 客户端获取/lock下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序 最小的子节点,如果是则认为获得锁,否则监听刚好在自己之前一位的子节点删除消息(避免惊群效应),获得子节点变更通知后重复此步骤直至获得锁;
3. 执行业务代码;
4. 完成业务流程后,删除对应的子节点释放锁。
ZooKeeper客户端Curator
Curator是Netflix公司开源的一套ZooKeeper客户端框架,解决了很多ZooKeeper客户端非常底层的细节开发工作,包括连接重连、反复注册Watcher和NodeExistsException异常等等。Patrixck Hunt(ZooKeeper)以一句“Guava is to Java that Curator to Zookeeper”给Curator予高度评价。
相关的依赖包:
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.7</version>
</dependency>
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!