文章转载自https://blog.csdn.net/koflance/article/details/78616206
1、互斥锁mutex lock
顾名思义就是排它锁,同一时间只允许一个客户端执行。
实现步骤:
- 首先,创建一个lock node,例如“locknode”
- 其次,客户端lock执行以下方式:
- 创建(create)一个有序临时节点,例如“locknode/guid-lock-”,其中guid可以是你客户端的唯一识别序号,如果发生前面说的创建失败问题,需要使用guid进行手动检查。
- 调用getChildren(watch=false)获取获取子节点列表,注意wtach设置为false,以避免羊群效应(Herd Effect),即同时收到太多无效节点删除通知。
- 从这个列表中,判断自己创建的节点序号是否是最小,如果是则直接返回true,否则继续往下走。
- 从步骤2中获取的list中选取排在当前节点前一位的节点,调用exist(watch=true)方法。
- 如果exist返回false,则回到步骤2;
- 如果exist返回true,则等待exist的哨兵(watch)回调通知,收到通知后再执行步骤2.
- 最后,客户端unlock只需要调用delete删除掉节点即可。
节点操作示意图:
流程图:
优点:
- 避免了轮询和超时控制
- 每次一个子节点的删除动作,只会触发唯一一个客户端的watch动作,从而避免了羊群效应
- 便于观测
缺点:
- 没有解决锁重入问题,因为采用的是有序临时节点,因此多次调用create并不会触发KeeperException.NodeExists异常,从而无法实现锁重入功能。如果需要解决,则在步骤1时,需要先进行判断当前节点是否已经存在,即调用getChildren(watch=false),判断当前节点是否已经创建(配合guid),已经创建,则直接从步骤3开始,没有创建则从步骤1开始。
- 这是一个公平锁,无法实现非公平锁。参考[4]实现了一个非公平锁
注意:
如果一个节点创建了一个sequential ephemeral nodes,但是在server返回创建成功的时候,server挂了,此时客户端需要重新连接,重新连接后会话依然有效,但其创建的临时节点却没有删除。解决方式就是在每次创建时create,如果发生失败,客户端需要getChildren(),进行手动检查是否获取锁,这个时候就需要使用guid。
2、共享锁Shared Locks或读写锁Read/Write Locks
Read读锁是共享锁,Write写锁是排它锁,当没有写时,允许多个read实例获取读锁,当有一个write实例获得写锁时,则不允许任何其他write实例和read实例获得锁。
实现步骤:
- 首先,创建一个lock node,例如“locknode”
- 获取read锁步骤:
- 创建(create)一个有序临时节点,例如“locknode/read-guid-lock-”,其中guid可以是你客户端的唯一识别序号,如果发生前面说的创建失败问题,需要使用guid进行手动检查。
- 调用getChildren(watch=false)获取获取子节点列表,注意wtach设置为false,以避免羊群效应(Herd Effect),即同时收到太多无效节点删除通知。
- 从这个列表中,判断是否有序号比自己小、且路径名以“write-”开头的节点,如果没有,则直接获取读锁,否则继续如下步骤。
- 从步骤2中获取的list中选取排在当前节点前一位的、且路径名以“write-”开头的节点,调用exist(watch=true)方法。
- 如果exist返回false,则回到步骤2。
- 如果exist返回true,则等待exist的哨兵(watch)回调通知,收到通知后再执行步骤2。
- 获取write锁步骤:
- 创建(create)一个有序临时节点,例如“locknode/write-guid-lock-”,其中guid可以是你客户端的唯一识别序号,如果发生前面说的创建失败问题,需要使用guid进行手动检查。
- 调用getChildren(watch=false)获取获取子节点列表,注意wtach设置为false,以避免羊群效应(Herd Effect),即同时收到太多无效节点删除通知。
- 从这个列表中,判断自己创建的节点序号是否是最小,如果是则直接返回true,否则继续往下走。
- 从步骤2中获取的list中选取排在当前节点前一位的节点,调用exist(watch=true)方法。
- 如果exist返回false,则回到步骤2;
- 如果exist返回true,则等待exist的哨兵(watch)回调通知,收到通知后再执行步骤2.
- 最后,客户端unlock只需要调用delete删除掉节点即可。
节点操作示意图:
流程图:
- read lock
- write lock
优点:
- 避免了轮询和超时控制
- 每次一个子节点的删除动作,只会触发唯一一个客户端的watch动作,从而避免了羊群效应
- 便于观测
缺点:
- 没有解决锁重入问题,因为采用的是有序临时节点,因此多次调用create并不会触发KeeperException.NodeExists异常,从而无法实现锁重入功能。如果需要解决,则在步骤1时,需要先进行判断当前节点是否已经存在,即调用getChildren(watch=false),判断当前节点是否已经创建(配合guid),已经创建,则直接从步骤3开始,没有创建则从步骤1开始。
- 当有非常多的read节点在等待一个write节点删除通知时,一旦write节点删除,将会触发非常多的read节点被调用,不过这种情况无法避免。
可撤销和超时问题
当前的读写锁并没有考虑读锁可撤销和超时问题,如何让读锁主动放弃,如何判断超时等,我想可行的方案还是在客户端自己处理,如果其他客户端想让前面的节点放弃锁,可以在节点写入unlock信息,让持有锁的客户端监听该变化,收到unlock信息,自己主动放弃对锁的持有。
3、参考
[1] http://zookeeper.apache.org/doc/trunk/recipes.html#sc_recipes_Locks
[2] http://blog.csdn.net/xubo_zhang/article/details/8506163
[3] http://netcome.iteye.com/blog/1474255 一个比较好的介绍
[4] http://blog.csdn.net/nimasike/article/details/51567653#reply 实现了一种非公平锁
来源:oschina
链接:https://my.oschina.net/u/3765527/blog/1837263