ZooKeeper实现分布式锁

837 阅读5分钟

分布式锁区别于本地锁,分布式锁通过网络通信协调多方组件有序运行。

zk分布式锁的优劣

  • 安全:串行化集群服务的并发请求,正确操作共享资源。

  • 限流:控制集群服务的高并发流量。

  • zk分布式锁的优势:大多数高可用(无单点故障问题)、顺序一致性(无同步数据一致性问题)。

  • zk分布式锁的劣势:性能较低(Leader节点串行化写操作和同步Follower节点)。


分布式锁的应用场景

1、以获取排行榜数据为例,假设查询服务部署在多台服务器上,此时缓存中的热点数据被删除,会导致缓存击穿,全部服务器上的查询请求都打到数据库,可能会使数据库宕机。

  • 给查询接口上一个分布式锁,查询请求发现缓存无数据,先抢分布式锁,抢到锁才能查询数据库,未抢到锁的就重新查询缓存,查不到就再抢锁。

2、以秒杀抢购100件商品为例,假设订单服务部署在多台服务器上,把秒杀请求负载均衡到多台服务器,全部服务器上的请求都打到数据库,同时扣减库存,可能会使数据库宕机。

  • 给秒杀接口上100个分布式锁,抢到锁的请求才能请求数据库扣减库存。

  • (其实挺复杂,还得考虑少卖问题和重复购买问题)


补充zk前置知识

zk拥有永久节点、临时节点、顺序永久节点和顺序临时节点四种节点。

  • 永久节点:节点创建之后永久保留在zk服务器上,直到主动删除节点;
  • 临时节点:节点的生命周期和客户端服务端之间的会话绑定,会话结束,节点自动删除(也可主动删除);
  • 顺序节点:根据节点创建先后,给节点添加一个单调递增的10位整型后缀(整型后缀从0000000000开始)。

zk拥有Watch机制,客户端可以向节点注册监听事件,以便事件发生收到通知。

  • 事件有四种,分别是:节点创建、节点删除、节点数据修改、子节点变更。

公平锁

基于 顺序临时节点 + Watch 机制实现:

  • 公平锁流程图如下:

公平锁流程图

  • 客户端获取公平锁的步骤(省略部分失败操作):

    • 步骤(1):使用路径名为 "/fairLock/id-" 创建顺序临时节点;
    • 步骤(2):获取"/fairLock"路径下所有子节点;
    • 步骤(3):比较节点后缀序号大小,如果自己节点的后缀序号是最小的,则获取到锁;如果不是最小,则监听前一个小的节点;
    • 步骤(4):持锁客户端执行后续逻辑,非持锁客户端等待zk通知。
  • 客户端释放公平锁的步骤:

    • 步骤(1):持锁客户端执行完后续逻辑,删除自己的节点(zk会通知监听该节点的客户端)。

非公平锁

基于 临时节点 + Watch 机制实现:

  • 非公平锁流程图如下:

非公平锁流程图

  • 客户端获取非公平锁的步骤(省略部分失败操作):

    • 步骤(1):使用路径名为 "unfairLock/id-" 创建临时节点;
    • 步骤(2):如果创建节点成功,则获取到锁;如果创建节点失败,则监听持锁节点。
  • 客户端释放非公平的步骤:

    • 步骤(1):持锁客户端执行完后续逻辑,删除自己的节点(zk会通知监听该节点的客户端)。

重入锁(略)

在公平锁和非公平锁的基础上,使用节点的value记录重入次数。


思考

1、为什么节点名需要携带唯一id?

  • (1)避免出现幽灵节点。假设客户端发送创建节点请求给zk,zk成功创建节点,但是发送响应给客户端前zk宕机了,客户端未收到响应并重发请求,无论重发请求是否创建成功,原请求创建的节点会被忽略,该节点即幽灵节点。而如果携带唯一id,可以在重发请求前先获取节点信息,查询节点是否创建成功。
  • (2)公平锁中获取路径下所有节点,也需要通过唯一id寻找自己的节点。

2、为什么使用临时节点,而不使用永久节点?

  • 临时节点的问题:

    • 如果客户端没有宕机,但是因为网络分区或长时间未响应心跳,会话断开并删除持锁节点,监听持锁节点的客户端会收到通知去抢锁,这样会出现多个客户端持锁的情况。
  • 永久节点的问题:

    • 如果持锁客户端释放锁之前宕机了,会出现锁无法释放的情况。
    • 不过在 zk 3.6.0 永久节点可以设置过期时间,可以通过设置过期时间来避免锁无法释放。
    • 如果设置过期时间,需要避免过期时间过长或过短,以及动态延长过期时间(需要考虑延长服务组件的可用性)。
    • 如果客户端未主动删除节点,但节点因为过期而删除(延长服务组件宕机),可能会出现多个客户端持锁的请求(也会出现节点误删)。
  • 总结:

    • 临时节点的通信断开,可以选择回滚之前的操作。

    • 临时节点和永久节点都会出现多个客户端持锁情况,倾向于选择更简单的方案(临时节点)。