Zookeeper分布式锁服务及总结

本篇分享文章,是对分布式锁服务的阶段性总结,大家可以去前面翻看相关文章,希望能给读者带来收获。

今天的目录如下:

  1. 分布式锁功能回顾

  2. 分布式锁实现思路

  3. zookeeper的简单介绍

  4. 实现分布式锁服务

1. 分布式锁功能回顾

前面我们已经分析过分布式锁服务的功能,大家有兴趣去看看《分布式锁服务的思考》,总结一下功能点:

  • 锁定服务的接口或资源,防止多个线程同时执行

  • 分布式锁服务本身需要具有高可用性

  • 可靠的获得锁以及释放锁

  • 锁具有超时机制,不会发生死锁,或者具有死锁检测机制

  • 同一个线程可以重复锁定资源,分布式锁具有可重入的特点

  • 客户端获得锁具有超时机制或重试机制。

2. 分布式锁实现思路

分布式锁服务实现方式有多种,其中比较流行的分布式锁实现有如下:

  • 结合数据库事务实现,只要数据库架构高可用,基本上能满足大多数的锁服务要求。但是需要重点考虑线程阻塞,且锁没有超时过期的特性。

  • 使用Redis实现。注意,我在前面的文章《Redis红锁不适合分布式锁服务》,Redis红锁并不适合做分布式锁服务,原因也分析了:5个节点太重,且不能解决线程的阻塞导致多个线程持有锁。还不如直接使用单机版本的setnx + lua(delete key) 这种方式来的干脆。(不管单机版的分布式锁还是红锁,都具有线程阻塞问题,详细说明见前面的两篇文章)

  • zookeeper实现分布式锁,今天我们重点说明zookeeper以及实现分布式锁。

数据库利用事务实现分布式锁服务比较简单,不在这里深入了。

Redis实现分布式锁服务请看前面两篇文章,不建议使用红锁。

下面开始我们今天的重点,使用zookeeper实现锁服务。(zk上年纪了,但它非常经典,有必要学习一下)

3. zookeeper的简单介绍

Zookeeper分布式锁服务及总结

服务间数据同步以及选举

比较重要的特点:

  1. 服务之间相互通信,在内存中维持了服务的状态图以及事务日志。

  2. 快照存储在硬盘中,多数可用,服务就可用。

  3. 因为存储使用内存,速度快,官网解释10比1的读写性能最佳。

  4. 事务顺序数字标记,具有顺序操作特性,原子性操作。

  5. 有层级命名空间,很像是分布式文件系统。唯一的区别就是每一个命名空间下的node节点以及它的children节点都可以关联数据。这就像电脑的文件系统,一个文件即存储数据,也可以作为一个目录。

  6. 节点分为永久节点和临时节点,临时节点生命同client的session一致,临时节点不允许有孩子。

  7. watch机制,监听节点的变化,一旦触发通知,watch就消失了,若想继续监听则必须重新set watch。

Zookeeper分布式锁服务及总结

数据节点模型

zookeeper常用的场景:

  • 统一命名空间

    dubbo服务提供者把自己的服务Url地址注册到,如/dubbo/${serviceName}/providers/目录下,完成服务的发布操作。dubbo服务消费者启动后,订阅/dubbo/${serviceName}/providers目录下的Url地址,并向/dubbo/${serviceName}/consumer/中写下自己的Url。所有的地址都是临时的节点,这样服务的提供者和消费者都能感知到资源的变化,dubbo可以监控/dobbo/${serviceName}/下面的所有服务者和消费者的信息。

  • 配置管理

    利用zk实现配置中心,在应用时首先要封装一套应用层的API方便业务逻辑使用,如果我们的服务比较少使用文件就可以满足,反之需要使用配置中心管理服务,例如实现服务降级开关。除了zookeeper,还有Etcd3、consul等,这里可告诉大家,zk实现配置管理比较重,管理复杂,不建议使用了。

  • 分布式集群管理

    zookeeper很早就出现了,是大数据hadoop生态圈的协调管理者。zk集群选举机制采用了zab协议,保证了分布式数据的一致性,并实现leader选举。

  • 分布式锁服务(强一致性)

    保持资源独占和严格的控制时序。使用一致性保证,可以很简单的构建一些高级的用法:客户端barriers、queues、以及可撤销的读写锁。

4. 实现分布式锁服务

使用zk实现分布式锁很简单,首先我们看下client获得锁的步骤:

  1. 参与锁竞争的client调用create()方法创建一个路径比如:‘locknode/lock-0000001’的有序临时节点。

  2. 在locknode节点上调用getChildren(),获得有序节点列表,为防止羊群效应,这里不可设置watch。

  3. 如果client在第一步创建的是列表中最小的后缀节点,那么该client获得锁并返回。

  4. 如果创建的节点序列,不是列表中最小的节点,则client调用exists()监听该节点的上一个节点。

  5. 返回null,重新返回step2执行。否则,等待通知获得锁的拥有权。

释放锁的流程很简单:只需要删除自己创建的节点。zk实现的分布式锁没有轮询,所以等待通知就好。

下面用代码说明上述过程:

1.在目录下创建临时有序节点的过程

Zookeeper分布式锁服务及总结

创建临时节点,注意失败重试

2.创建目录之后,可以直接获得临时节点名称。这里看下demo通过session获得节点的方法

Zookeeper分布式锁服务及总结

通过session前缀得到临时节点名字

Zookeeper分布式锁服务及总结

封装的对象

为了方便临时节点的表示,demo给出一个封装对象:ZNodeName。实现compare方法比较大小,方便排序

3.获取lock(dir)目录下的所有children,封装ZNodeName对象并放入Set中

Zookeeper分布式锁服务及总结

获取dir目录下的所有临时节点,放入Set中

4.比较当前client是否是最小的节点

lessThanMe.isEmpty() //表示当前session创建的节点具有最小的suffix

Zookeeper分布式锁服务及总结

获得锁返回成功

5.当前节点并非最小值,就监听当前临时节点的上一个临时节点。例如当前节点是003,那么就监听002。

Zookeeper分布式锁服务及总结

监听上一个节点的变化

等待上一个节点的释放触发LockWatcher,再去执行一遍获得锁的逻辑。

如果上一个节点不存在,本次获得锁失败。

Zookeeper分布式锁服务及总结

LockWatcher的逻辑

以上代码呢,大家只要能看懂大概zk的实现方式即可,为什么只是了解呢?

  • 首先zookeeper开发比较重,就是不方便。例如,简单的创建zk对象,就需要阻塞等待通知。

  • 另外,我们在应用中可直接使用apache封装好的工具curator就可以了,使用简单,学习成本低。

    • acquire

    • release

5. 总结

  • 在分布式应用中,需要防止并发访问资源造成数据不一致,需可重入分布式锁。

  • 常用的分布式锁实现。mysql默认Repeatable Read事务隔离以及Redis setnx +lua delete。

  • 简单介绍zk,了解即可,不需要深入了。

  • 代码介绍原理,了解接即可,因为直接使用curator更方便

希望本文能给大家带来收获,如果有疑问或者觉得表述不正确,可在评论中指出。


分享到:


相關文章: