ZooKeeper分布式锁
什么是分布式锁?
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥手段来防止彼此之间的干扰,以保证一致性,在这种情况下,就需要使用分布式锁了。
Zookeeper分布式锁方案
Zookeeper 是通过创建 临时节点/临时顺序节点 的方式来实现分布式锁的加锁。
下面介绍ZooKeeper如何实现排他锁和共享锁两类分布式锁。
1、排他锁:(X锁),又称为写锁或独占锁,是一种基本的锁类型,如果事务T1对数据O1加上了排他锁,那么在这个加锁期间,只允许事务T1对O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作,直到T1释放了锁。
定义锁:在Java中,有Synchronized机制和ReentrantLock来定义锁,在ZooKeeper中没有类似于这样的API可以使用,而是通过ZooKeeper上的数据节点来表示一个锁,例如/exclusive_lock/lock就可以被定义为一个锁,不同的机器通过抢占创建临时节点的方式获取锁,会话失效则释放锁。
获取锁:在需要获取排他锁时,所有的客户端都会试图通过调用create()接口,在/exclusice_lock节点下创建临时子节点 /exclusive_lock/lock。由于ZooKeeper会保证所有的客户端中只有一个客户端能创建成功,那么,只要客户端创建成功,就可以认为获取到了锁。同时,没有获得锁的客户端需要在 /exclusive_lock节点下注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。
释放锁:由于 客户端创建的/exclusive_lock/lock节点是一个临时节点,因此下面两种情况都有可能释放锁:
1)当前获取锁的客户端机器发生宕机,那么ZooKeeper上的这个临时节点就会被移除
2)正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除
如果lock节点被删除,那么ZooKeeper就会通知所有在 /exclusive_lock节点上注册了子节点变更的Watcher监听的客户端,这些客户端在接收到通知后,重新发起发布式锁的获取。
2、共享锁:(S锁),又称为读锁,如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁,而不能修改这条数据,直到该数据对象上的所有共享锁都被释放。
定义锁:和排他锁一样,同样是通过ZooKeeper上的数据节点来表示一个锁,是一个类似于/share_lock/[hostname]-请求类型-请求序号的临时顺序节点,如/share_lock/192.168.0.1-R-00001,那么这个节点就代表了一个共享锁。
获取锁:在需要获取共享锁时,所有的客户端都会到/share_lock这个节点下创建一个临时顺序节点,如果当前是读请求,那么就创建例如 /share_lock/192.168.0.1-R-00001的节点,如果是写请求,就会创建如/share_lock/192.168.0.1-W-00001 的节点
判断读写顺序: 根据共享锁的定义,不同的事务都可以对同一个数据对象进行读取操作,而更新操作必须在当前没有任务事务进行读写操作的情况下进行。基于这个原则,来看一下如何通过ZK的节点来确定分布式读写顺序,大致分为4个步骤:
1):创建完节点后,获取/share_lock节点下的所有子节点列表,并对该节点注册子节点变更的Watcher监听。
2):确定自己的节点序号在所有子节点列表中的顺序
3):对于读请求:如果没有比 自己序号小的子节点(第一个创建的子节点),或是所有的比自己序号小的节点都是读请求,那么表名自己已经成功获取到了共享锁,同时开始执行读取逻辑;如果比自己序号小的子节点中有写请求,那么就需要进入等待
对于写请求:如果自己不是序号最小的子节点,那么就需要进入等待
4):接收到Watcher通知后,重复步骤(1)
但是有个问题,如果集群规模比较大,大量的机器在/share_lock节点下创建子节点,并注册Watcher监听,每一次Watcher监听都判断自己在子节点列表中的顺序,如果是写操作则判断有没有比自己序号小的节点,读操作也要判断比自己小的节点的操作类型,如果没有轮到自己操作,则继续等待下一次通知。如果集群规模比较的情况下,同一时间点有多个节点对应的客户端完成事务或是中断使节点删除,则zk就会在短时间内向其余客户端发送大量的事件通知,这就是所谓的羊群效应。
改进后的分布式锁实现:找准客户端真正的关注点,客户端每次都是判断自己是否是所有子节点中序号最小的,因此不用关注/share_lock节点下的所有子节点的变更,只需要关注比自己节点序号小的子节点的变更情况就可以了。
释放锁:和排他锁一致。
END.