ZK分布式锁
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或者是同一个系统的不同主机之间共享了一个或者一组资源,那么访问这些资源的时候,往往需要一些互斥手段来防止彼此之间的干扰,以保证一致性,在这种情况下,就需要分布式锁了 实现分布式锁的方案1 原生zk方案Zookeeper中有一种节点叫做顺序节点,假如我们在/lock/目录下创建节3个点,ZooKeeper集群会按照...
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或者是同一个系统的不同主机之间共享了一个或者一组资源,那么访问这些资源的时候,往往需要一些互斥手段来防止彼此之间的干扰,以保证一致性,在这种情况下,就需要分布式锁了
实现分布式锁的方案
1 原生zk方案
Zookeeper中有一种节点叫做顺序节点,假如我们在/lock/目录下创建节3个点,ZooKeeper集群会按照提起创建的顺序来创建节点,节点分别为/lock/0000000001、/lock/0000000002、/lock/0000000003。
ZooKeeper中还有一种名为临时节点的节点,临时节点由某个客户端创建,当客户端与ZooKeeper集群断开连接,则开节点自动被删除。
EPHEMERAL_SEQUENTIAL为临时顺序节点
实现分布式锁的基本逻辑:
- 客户端调用create()方法创建名为“locknode/guid-lock-”的节点,需要注意的是,这里节点的创建类型需要设置为EPHEMERAL_SEQUENTIAL。
- 客户端调用getChildren(“locknode”)方法来获取所有已经创建的子节点。
- 客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点是所有节点中序号最小的,那么就认为这个客户端获得了锁。
- 如果创建的节点不是所有节点中需要最小的,那么则监视比自己创建节点的序列号小的最大的节点,进入等待。直到下次监视的子节点变更的时候,再进行子节点的获取,判断是否获取锁。
释放锁的过程相对比较简单,就是删除自己创建的那个子节点即可。
以下为流程图
还有一个共享锁,和排他锁一样,同样是通过zk上的数据节点来表示一个锁,是一个类似于/shared_lock/[hostname]-请求类型-序号“的临时节点,例如//shared_lock/192.168.1.1-R-0000001,那么这个就代表了一个共享锁
步骤:
1 客户端调用create()方法创建一个类似于“/shared_lock/[Hostname]-请求类型-序号的临时顺序节点
2 客户端调用getChildren()接口来获取所有已经创建的字节点列表,注意这里不注册任何的wather
3 如果无法获得获取共享锁,那么就调用exist()来对比自己小的那个节点注册wather
如果是读请求:向比自己小的最后一个写请求节点注册watcher监听
如果是写请求:向比自己序号小的最后一个节点注册watcher监听
4等待watcher通知,继续进入步骤2
以下为流程图
2 Curator方案
封装了zk的客户端,其分布式实现方式和上面的基本相同。同时还提供了不同的锁类型:
可重入锁:实现类为InterProcessMutex,将线程对象,节点,锁对象相关联。InterProcessMutex内部维护了一个使用线程为key,{thread,path}为值的map,所以对不同的线程和请求加锁的节点进行一一对应。提供方法acquire 和 release。
不可重入锁:实现类为InterProcessSemaphoreMutex,类似InterProcessMutex,只是没有维护线程的map。
可重入读写锁:类似JDK的ReentrantReadWriteLock
.一个读写锁管理一对相关的锁。 主要由两个类实现:
- InterProcessReadWriteLock
- InterProcessLock
使用时首先创建一个InterProcessReadWriteLock
实例,然后再根据你的需求得到读锁或者写锁, 读写锁的类型是InterProcessLock
。
读写锁的实现与互斥锁类似,不同的地方在于创建自节点时读锁和写锁要区分类型。例如读锁的前缀可以设置为read,写锁的前缀可以设置为write。创建读锁的时候,检查是否有编号小于自己的写锁存在,若存在则对编号刚好小于自己的写锁节点进行监听。创建写锁时,检查创建的节点编号是否为最小,如不是最小,则需要对编号刚好小于自己的节点进行监听(此时不区分读锁和写锁)
还有信号量和多锁对象。
需要注意的地方
node节点选择为EPHEMERAL_SEQUENTIAL很重要。
自增长的特性,可以方便构建一个基于Fair特性的锁
,前一个节点唤醒后一个节点,形成一个链式的触发过程。可以有效的避免"惊群效应"(一个锁释放,所有等待的线程都被唤醒)
,有针对性的唤醒,提升性能。
选择一个EPHEMERAL临时节点的特性
。因为和zookeeper交互是一个网络操作,不可控因素过多,比如网络断了,上一个节点释放锁的操作会失败。临时节点是和对应的session挂接的,session一旦超时或者异常退出其节点就会消失,类似于ReentrantLock中等待队列Thread的被中断处理
。
获取lock操作是一个阻塞的操作,而对应的Watcher是一个异步事件
,所以需要使用互斥信号共享锁BooleanMutex进行通知
,可以比较方便的解决锁重入的问题。(锁重入可以理解为多次读操作,锁释放为写抢占操作)
2 使用EPHEMERAL会引出一个风险:在非正常情况下,网络延迟比较大会出现session timeout,zookeeper就会认为该client已关闭,从而销毁其id标示,竞争资源的下一个id就可以获取锁
。这时可能会有两个process同时拿到锁在跑任务
,所以设置好session timeout很重要
zk实现分布式锁的优缺点
优点:
- 锁安全性高,zk可持久化,且能实时监听获取锁的客户端状态。一旦客户端宕机,则瞬时节点随之消失,zk因而能第一时间释放锁。这也省去了用分布式缓存实现锁的过程中需要加入超时时间判断的这一逻辑。
- zookeeper支持watcher机制,这样实现阻塞锁,可以watch锁数据,等到数据被删除,zookeeper会通知客户端去重新竞争锁。
- zookeeper的数据可以支持临时节点的概念,即客户端写入的数据是临时数据,在客户端宕机后,临时数据会被删除,这样就实现了锁的异常释放。使用这样的方式,就不需要给锁增加超时自动释放的特性了。
缺点:
- 性能开销比较高。因为其需要动态产生、销毁瞬时节点来实现锁功能。所以不太适合直接提供给高并发的场景使用。
要避免zk分布式锁的羊群效应 http://aliapp.blog.51cto.com/8192229/1328018
更多推荐
所有评论(0)