本篇分享文章,是对分布式锁服务的阶段性总结,大家可以去前面翻看相关文章,希望能给读者带来收获。
今天的目录如下:
-
分布式锁功能回顾
-
分布式锁实现思路
-
Zookeeper的简单介绍
-
实现分布式锁服务
前面我们已经分析过分布式锁服务的功能,大家有兴趣去看看《分布式锁服务的思考》,总结一下功能点:
-
锁定服务的接口或资源,防止多个线程同时执行
-
分布式锁服务本身需要具有高可用性
-
可靠的获得锁以及释放锁
-
锁具有超时机制,不会发生死锁,或者具有死锁检测机制
-
同一个线程可以重复锁定资源,分布式锁具有可重入的特点
-
客户端获得锁具有超时机制或重试机制。
分布式锁服务实现方式有多种,其中比较流行的分布式锁实现有如下:
-
结合数据库事务实现,只要数据库架构高可用,基本上能满足大多数的锁服务要求。但是需要重点考虑线程阻塞,且锁没有超时过期的特性。
-
使用Redis实现。注意,我在前面的文章《Redis红锁不适合分布式锁服务》,Redis红锁并不适合做分布式锁服务,原因也分析了:5个节点太重,且不能解决线程的阻塞导致多个线程持有锁。还不如直接使用单机版本的setnx lua(delete key) 这种方式来的干脆。(不管单机版的分布式锁还是红锁,都具有线程阻塞问题,详细说明见前面的两篇文章)
-
zookeeper实现分布式锁,今天我们重点说明zookeeper以及实现分布式锁。
数据库利用事务实现分布式锁服务比较简单,不在这里深入了。
服务间数据同步以及选举
比较重要的特点:
服务之间相互通信,在内存中维持了服务的状态图以及事务日志。
快照存储在硬盘中,多数可用,服务就可用。
因为存储使用内存,速度快,官网解释10比1的读写性能最佳。
事务顺序数字标记,具有顺序操作特性,原子性操作。
有层级命名空间,很像是分布式文件系统。唯一的区别就是每一个命名空间下的node节点以及它的children节点都可以关联数据。这就像电脑的文件系统,一个文件即存储数据,也可以作为一个目录。
节点分为永久节点和临时节点,临时节点生命同client的session一致,临时节点不允许有孩子。
watch机制,监听节点的变化,一旦触发通知,watch就消失了,若想继续监听则必须重新set watch。
数据节点模型
zookeeper常用的场景:
4. 实现分布式锁服务
统一命名空间
dubbo服务提供者把自己的服务Url地址注册到,如/dubbo/${serviceName}/providers/目录下,完成服务的发布操作。dubbo服务消费者启动后,订阅/dubbo/${serviceName}/providers目录下的Url地址,并向/dubbo/${serviceName}/consumer/中写下自己的Url。所有的地址都是临时的节点,这样服务的提供者和消费者都能感知到资源的变化,dubbo可以监控/dobbo/${serviceName}/下面的所有服务者和消费者的信息。
配置管理
利用zk实现配置中心,在应用时首先要封装一套应用层的API方便业务逻辑使用,如果我们的服务比较少使用文件就可以满足,反之需要使用配置中心管理服务,例如实现服务降级开关。除了zookeeper,还有Etcd3、consul等,这里可告诉大家,zk实现配置管理比较重,管理复杂,不建议使用了。
分布式集群管理
zookeeper很早就出现了,是大数据hadoop生态圈的协调管理者。zk集群选举机制采用了zab协议,保证了分布式数据的一致性,并实现leader选举。
分布式锁服务(强一致性)
保持资源独占和严格的控制时序。使用一致性保证,可以很简单的构建一些高级的用法:客户端barriers、queues、以及可撤销的读写锁。
使用zk实现分布式锁很简单,首先我们看下client获得锁的步骤:
参与锁竞争的client调用create()方法创建一个路径比如:‘locknode/lock-0000001’的有序临时节点。
在locknode节点上调用getChildren(),获得有序节点列表,为防止羊群效应,这里不可设置watch。
如果client在第一步创建的是列表中最小的后缀节点,那么该client获得锁并返回。
如果创建的节点序列,不是列表中最小的节点,则client调用exists()监听该节点的上一个节点。
返回null,重新返回step2执行。否则,等待通知获得锁的拥有权。
释放锁的流程很简单:只需要删除自己创建的节点。zk实现的分布式锁没有轮询,所以等待通知就好。
下面用代码说明上述过程:
1.在目录下创建临时有序节点的过程
创建临时节点,注意失败重试
2.创建目录之后,可以直接获得临时节点名称。这里看下demo通过session获得节点的方法
通过session前缀得到临时节点名字
封装的对象
为了方便临时节点的表示,demo给出一个封装对象:ZNodeName。实现compare方法比较大小,方便排序
3.获取lock(dir)目录下的所有children,封装ZNodeName对象并放入Set中
获取dir目录下的所有临时节点,放入Set中
4.比较当前client是否是最小的节点
lessThanMe.isEmpty() //表示当前session创建的节点具有最小的suffix
获得锁返回成功
5.当前节点并非最小值,就监听当前临时节点的上一个临时节点。例如当前节点是003,那么就监听002。
监听上一个节点的变化
等待上一个节点的释放触发LockWatcher,再去执行一遍获得锁的逻辑。
如果上一个节点不存在,本次获得锁失败。
LockWatcher的逻辑
以上代码呢,大家只要能看懂大概zk的实现方式即可,为什么只是了解呢?
5. 总结
首先zookeeper开发比较重,就是不方便。例如,简单的创建zk对象,就需要阻塞等待通知。
另外,我们在应用中可直接使用apache封装好的工具curator就可以了,使用简单,学习成本低。
acquire
release
在分布式应用中,需要防止并发访问资源造成数据不一致,需可重入分布式锁。
常用的分布式锁实现。mysql默认Repeatable Read事务隔离以及Redis setnx lua delete。
简单介绍zk,了解即可,不需要深入了。
代码介绍原理,了解接即可,因为直接使用curator更方便
希望本文能给大家带来收获,如果有疑问或者觉得表述不正确,可在评论中指出。
,