拜托,别再问我 Zookeeper 如何实现分布式锁了!
导读
真是有人(
锁
)的地方就有江湖(事务
),今天不谈江湖,来撩撩人。分布式锁的概念、为什么使用分布式锁,想必大家已经很清楚了。前段时间作者写过Redis是如何实现分布式锁,今天这篇文章来谈谈Zookeeper是如何实现分布式锁的。
陈某今天分别从如下几个方面来详细讲讲ZK如何实现分布式锁:ZK的四种节点排它锁的实现读写锁的实现Curator实现分步式锁
ZK的四种节点
持久性节点:节点创建后将会一直存在
临时节点:临时节点的生命周期和当前会话绑定,一旦当前会话断开临时节点也会删除,当然可以主动删除。
持久有序节点:节点创建一直存在,并且zk会自动为节点加上一个自增的后缀作为新的节点名称。
临时有序节点:保留临时节点的特性,并且zk会自动为节点加上一个自增的后缀作为新的节点名称。
排它锁的实现
排他锁的实现相对简单一点,利用了zk的创建节点不能重名的特性。如下图:
根据上图分析大致分为如下步骤:尝试获取锁:创建
临时节点
,zk会保证只有一个客户端创建成功。创建临时节点成功,获取锁成功,执行业务逻辑,业务执行完成后删除锁。创建临时节点失败,阻塞等待。监听删除事件,一旦临时节点删除了,表示互斥操作完成了,可以再次尝试获取锁。递归:获取锁的过程是一个递归的操作,获取锁->监听->获取锁
。如何避免死锁:创建的是临时节点,当服务宕机会话关闭后临时节点将会被删除,锁自动释放。
代码实现
作者参照JDK锁的实现方式加上模板方法模式的封装,封装接口如下:
模板抽象类如下:
排他锁的具体实现类如下:
可重入性排他锁如何设计
可重入的逻辑很简单,在本地保存一个
ConcurrentMap
,key
是当前线程,value
是定义的数据,结构如下:
重入的伪代码如下:
读写锁的实现
读写锁分为读锁和写锁,区别如下:读锁允许多个线程同时读数据,但是在读的同时不允许写线程修改。写锁在获取后,不允许多个线程同时写或者读。
如何实现读写锁?ZK中有一类节点叫临时有序节点,上文有介绍。下面我们来利用临时有序节点来实现读写锁的功能。
读锁的设计
读锁允许多个线程同时进行读,并且在读的同时不允许线程进行写操作,实现原理如下图:
根据上图,获取一个读锁分为以下步骤:创建临时有序节点(当前线程拥有的
读锁
或称作读节点
)。获取路径下所有的子节点,并进行从小到大
排序获取当前节点前的临近写节点(写锁)。如果不存在的临近写节点,则成功获取读锁。如果存在临近写节点,对其监听删除事件。一旦监听到删除事件,重复2,3,4,5的步骤(递归)。
写锁的设计
线程一旦获取了写锁,不允许其他线程读和写。实现原理如下:
从上图可以看出唯一和写锁不同的就是监听的节点,这里是监听临近节点(读节点或者写节点),读锁只需要监听写节点,步骤如下:创建临时有序节点(当前线程拥有的
写锁
或称作写节点
)。获取路径下的所有子节点,并进行从小到大
排序。获取当前节点的临近节点(读节点和写节点)。如果不存在临近节点,则成功获取锁。如果存在临近节点,对其进行监听删除事件。一旦监听到删除事件,重复2,3,4,5的步骤(递归)。
如何监听
无论是写锁还是读锁都需要监听前面的节点,不同的是读锁只监听临近的写节点,写锁是监听临近的所有节点,抽象出来看其实是一种链式的监听,如下图:
每一个节点都在监听前面的临近节点,一旦前面一个节点删除了,再从新排序后监听前面的节点,这样递归下去。
代码实现
作者简单的写了读写锁的实现,先造出来再优化,不建议用在生产环境。代码如下:
Curator实现分步式锁
Curator是Netflix公司开源的一个Zookeeper客户端,与Zookeeper提供的原生客户端相比,Curator的抽象层次更高,简化了Zookeeper客户端的开发量。
Curator在分布式锁方面已经为我们封装好了,大致实现的思路就是按照作者上述的思路实现的。中小型互联网公司还是建议直接使用框架封装好的,毕竟稳定,有些大型的互联公司都是手写的,牛逼啊。
创建一个排他锁很简单,如下:
更多的API请参照官方文档,不是此篇文章重点。
至此ZK实现分布式锁就介绍完了,如有想要源码的朋友,老规矩,回复关键词
分布式锁
获取。
一点小福利
对于Zookeeper不太熟悉的朋友,陈某特地花费两天时间总结了ZK的常用知识点,包括ZK常用shell命令、ZK权限控制、Curator的基本操作API。目录如下:
需要上面PDF文件的朋友,老规矩,回复关键词
ZK总结
。
版权声明: 本文为 InfoQ 作者【不才陈某】的原创文章。
原文链接:【http://xie.infoq.cn/article/28573876869e911bcaf9e250d】。文章转载请联系作者。
评论 (13 条评论)