zookeeper分布式锁实现原理

    文章转载自https://blog.csdn.net/koflance/article/details/78616206html

一、互斥锁mutex lock

顾名思义就是排它锁,同一时间只容许一个客户端执行。node

实现步骤:apache

  • 首先,建立一个lock node,例如“locknode
  • 其次,客户端lock执行如下方式: 
    1. 建立(create)一个有序临时节点,例如“locknode/guid-lock-”,其中guid能够是你客户端的惟一识别序号,若是发生前面说的建立失败问题,须要使用guid进行手动检查。
    2. 调用getChildren(watch=false)获取获取子节点列表,注意wtach设置为false,以免羊群效应(Herd Effect),即同时收到太多无效节点删除通知。
    3. 从这个列表中,判断本身建立的节点序号是不是最小,若是是则直接返回true,不然继续往下走。
    4. 从步骤2中获取的list中选取排在当前节点前一位的节点,调用exist(watch=true)方法。
    5. 若是exist返回false,则回到步骤2;
    6. 若是exist返回true,则等待exist的哨兵(watch)回调通知,收到通知后再执行步骤2.
  • 最后,客户端unlock只须要调用delete删除掉节点便可。

节点操做示意图:ui

这里写图片描述

流程图:spa

这里写图片描述

优势.net

  • 避免了轮询和超时控制
  • 每次一个子节点的删除动做,只会触发惟一一个客户端的watch动做,从而避免了羊群效应
  • 便于观测

缺点server

  • 没有解决锁重入问题,由于采用的是有序临时节点,所以屡次调用create并不会触发KeeperException.NodeExists异常,从而没法实现锁重入功能。若是须要解决,则在步骤1时,须要先进行判断当前节点是否已经存在,即调用getChildren(watch=false),判断当前节点是否已经建立(配合guid),已经建立,则直接从步骤3开始,没有建立则从步骤1开始。
  • 这是一个公平锁,没法实现非公平锁。参考[4]实现了一个非公平锁

注意:htm

若是一个节点建立了一个sequential ephemeral nodes,可是在server返回建立成功的时候,server挂了,此时客户端须要从新链接,从新链接后会话依然有效,但其建立的临时节点却没有删除。解决方式就是在每次建立时create,若是发生失败,客户端须要getChildren(),进行手动检查是否获取锁,这个时候就须要使用guid。blog

二、共享锁Shared Locks或读写锁Read/Write Locks

Read读锁是共享锁,Write写锁是排它锁,当没有写时,容许多个read实例获取读锁,当有一个write实例得到写锁时,则不容许任何其余write实例和read实例得到锁。图片

实现步骤:

  • 首先,建立一个lock node,例如“locknode
  • 获取read锁步骤: 
    1. 建立(create)一个有序临时节点,例如“locknode/read-guid-lock-”,其中guid能够是你客户端的惟一识别序号,若是发生前面说的建立失败问题,须要使用guid进行手动检查。
    2. 调用getChildren(watch=false)获取获取子节点列表,注意wtach设置为false,以免羊群效应(Herd Effect),即同时收到太多无效节点删除通知。
    3. 从这个列表中,判断是否有序号比本身小、且路径名以“write-”开头的节点,若是没有,则直接获取读锁,不然继续以下步骤。
    4. 从步骤2中获取的list中选取排在当前节点前一位的、且路径名以“write-”开头的节点,调用exist(watch=true)方法。
    5. 若是exist返回false,则回到步骤2。
    6. 若是exist返回true,则等待exist的哨兵(watch)回调通知,收到通知后再执行步骤2。
  • 获取write锁步骤: 
    1. 建立(create)一个有序临时节点,例如“locknode/write-guid-lock-”,其中guid能够是你客户端的惟一识别序号,若是发生前面说的建立失败问题,须要使用guid进行手动检查。
    2. 调用getChildren(watch=false)获取获取子节点列表,注意wtach设置为false,以免羊群效应(Herd Effect),即同时收到太多无效节点删除通知。
    3. 从这个列表中,判断本身建立的节点序号是不是最小,若是是则直接返回true,不然继续往下走。
    4. 从步骤2中获取的list中选取排在当前节点前一位的节点,调用exist(watch=true)方法。
    5. 若是exist返回false,则回到步骤2;
    6. 若是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信息,本身主动放弃对锁的持有。

三、参考

[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 实现了一种非公平锁

相关文章
相关标签/搜索