1. 分布式锁的介绍

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥手段来防止彼此之间的干扰,以保证一致性,在这种情况下,就需要使用分布式锁了。

在平时的实际项目开发中,我们往往很少会去在意分布式锁,而是依赖于关系型数据库固有的排他性来实现不同进程之间的互斥。这确实是一种非常简便且被广泛使用的分布式锁实现方式。然而有一个不争的事实是,目前绝大多数大型分布式系统的性能瓶颈都集中在数据库操作上。因此,如果上层业务再给数据库添加一些额外的锁,例如行锁、表锁甚至是繁重的事务处理,那么就会让数据库更加不堪重负。

2. 排它锁

排它锁(Exclusive Locks,简称X锁),又称为写锁或独占锁,是一种基本的锁类型。如果事务T1对数据对象 O1加上了排它锁,那么在整个加锁期间,只允许事务T1对O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作——直到T1释放了排它锁

从上面的基本概念中,我们可以看到,排他锁的核心是如何保证当前有且仅有一个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都能够被通知到。

2.1 借助ZooKeeper实现排它锁

  • 定义锁
    在通常的Java开发编程中,有两种常见的方式可以用来定义锁,分别是synchronized 机制和 JDK5 提供的 ReentrantLock。然而,在ZooKeeper中,没有类似于这样的API可以直接使用,而是通过 ZooKeeper 上的数据节点来表示一个锁,例如 /exclusive_lock/lock 节点就可以被定义为一个锁,如图∶

  • 获取锁
    在需要获取排它锁时,所有的客户端都会试图通过调用 create() 方法,在 /exclusive_lock 节点下创建临时子节点 /exclusive_lock/lock。在前面,我们也介绍了,Zookeeper会保证在所有的客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获取了锁。同时,所有没有获取到锁的客户端就需要到 /exclusive_lock 节点上注册一个子节点变更的Watcher监听,以便实时监听到 lock节点的变更情况

  • 释放锁
    在"定义锁"部分,我们已经提到,/exclusive_lock/lock 是一个临时节点,因此在以下两种情况下,都有可能释放锁。当前获取锁的客户端机器发生宕机,那么ZooKeeper上的这个临时节点就会被移除。正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除。无论在什么情况下移除了lock节点,Zookeeper都会通知所有在 /exclusive_lock 节点上注册了子节点变更Watcher监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复"获取锁"过程。整个排他锁的获取和释放流程,如下图∶

3. 共享锁

共享锁(Shared Locks,简称S锁),又称为读锁,同样是一种基本的锁类型。
如果事务T1对数据对象O1加上了共享锁,那么当前事务只能对O1进行读取操作,其他事务也只能对这个数据对象加共享锁——直到该数据对象上的所有共享锁都被释放。

共享锁和排他锁最根本的区别在于,加上排他锁后,数据对象只对一个事务可见,而加上共享锁后,数据对所有事务都可见。

3.1 借助ZooKeeper实现共享锁

  • 定义锁
    和排他锁一样,同样是通过ZooKeeper上的数据节点来表示一个锁,是一个类似于 “/shared_lock/【Hostname】请求类型-序号” 的临时顺序节点,例如 /shared_lock/host1-R-000000001,那么,这个节点就代表了一个共享锁,如图所示∶
  • 获取锁
    在需要获取共享锁时,所有客户端都会到 /shared_lock 这个节点下面创建一个临时顺序节点,如果当前是读请求,那么就创建例如 /shared_lock/host1-R-00000001 的节点;如果是写请求,那么就创建例如/shared_lock/host2-W-0000002 的节点。

判断读写顺序

通过 Zookeeper 来确定分布式读写顺序,大致分为四步

1. 创建完节点后,获取 /shared_lock 节点下所有子节点,并对该节点变更注册监听。
2. 确定自己的节点序号在所有子节点中的顺序。
3. 对于读请求∶若没有比自己序号小的子节点或所有比自己序号小的子节点都是读请求,那么表明自己已经成功获取到共享锁,同时开始执行读取逻辑,若有写请求,则需要等待。对于写请求∶若自己不是序号最小的子节点,那么需要等待。
4. 接收到 watcher 通知后,重复步骤1
  • 释放锁
    其释放锁的流程与独占锁一致

3.2 羊群效应

上面的共享锁实现,大体上能够满足一般的分布式集群竞争锁的需求,并且性能都还可以——这里说的一般场景是指集群规模不是特别大,一般是在10台机器以内。但是如果机器规模扩大之后,会有什么问题呢?我们着重来看上面"判断读写顺序"过程的步骤3,结合下面的图,看看实际运行中的情况

针对如上图所示的情况进行分析

  1. host1首先进行读操作,完成后将节点/shared_lock/host1-R0000001删除。
  2. 余下4台机器均收到这个节点移除的通知,然后重新从/shared_lock节点上获取一份新的子节点列表。
  3. 每台机器判断自己的读写顺序,其中host2检测到自己序号最小,于是进行写操作,余下的机器则继续等待。
  4. 继续…

可以看到,host1 客户端在移除自己的共享锁后,Zookeeper 发送了子节点更变Watcher 通知给所有机器,然而除了给 host2 产生影响外,对其他机器没有任何作用。大量的 Watcher 通知和子节点列表获取两个操作会重复运行,这样不仅会对 zookeeper 服务器造成巨大的性能影响影响和网络开销,更为严重的是,如果同一时间有多个节点对应的客户端完成事务或是事务中断引起节点消失,ZooKeeper服务器就会在短时间内向其余客户端发送大量的事件通知,这就是所谓的羊群效应。

上面这个ZooKeeper分布式共享锁实现中出现羊群效应的根源在于,没有找准客户端真正的关注点。我们再来回顾一下上面的分布式锁竞争过程,它的核心逻辑在于∶判断自己是否是所有子节点中序号最小的。于是,很容易可以联想到,每个节点对应的客户端只需要关注比自己序号小的那个相关节点的变更情况就可以了——而不需要关注全局的子列表变更情况。

改进后的分布式锁实现
首先,我们需要肯定的一点是,上面提到的共享锁实现,从整体思路上来说完全正确。这里主要的改动在于∶每个锁竞争者,只需要关注 /shared_lock 节点下序号比自己小的那个节点是否存在即可,具体实现如下。

  1. 客户端调用create方法创建类似于/shared_lock【Hostname】-请求类型-序号 的临时顺序节点。
  2. 客户端调用 getChildren 方法获取所有已经创建的子节点列表(不注册任何Watcher)。
  3. 如果无法获取共享锁,就调用exist方法来对比自己小的节点注册Watcher。对于读请求∶向比自己序号小的最后一个写请求节点注册Watcher监听。对于写请求∶向比自己序号小的最后一个节点注册Watcher监听。
  4. 等待Watcher通知,继续进入步骤2。

此方案改动主要在于∶每个锁竞争者,只需要关注 /shared_lock 节点下序号比自己小的那个节点是否存在即可。

注意: 相信大家都会觉得改进后的分布式锁实现相对来说比较麻烦。确实如此,如同在多线程并发编程实践中,我们会去尽量缩小锁的范围——对于分布式锁实现的改进其实也是同样的思路。那么对于开发人员来说,是否必须按照改进后的思路来设计实现自己的分布式锁呢?答案是否定的。在具体的实际开发过程中,我们提倡根据具体的业务场景和集群规模来选择适合自己的分布式锁实现∶在集群规模不大、网络资源丰富的情况下,第一种分布式锁实现方式是简单实用的选择;而如果集群规模达到一定程度,并且希望能够精细化地控制分布式锁机制,那么就可以试试改进版的分布式锁实现。

Zookeeper学习:Zookeeper应用场景之分布式锁相关推荐

  1. ZooKeeper学习-- Zookeeper简单介绍

    前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家.点击跳转到教程. 一.分布式协调技术 在给大家介绍ZooKeeper之前先来给大家介绍一种技术--分布式协调技术.那么 ...

  2. 关于分布式锁原理的一些学习与思考:redis分布式锁,zookeeper分布式锁

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:牛人 20000 字的 Spring Cloud 总结,太硬核了~ 作者:队长给我球. 出处:https://w ...

  3. zookeeper学习03 使用场景

    zookeeper实际应用场景 zookeeper能够实现哪些场景 1)订阅发布/配置中心 watcher机制 统一配置管理(disconf) 实现配置信息的集中式原理和数据的动态更新 实现配置中心有 ...

  4. Zookeeper,etcd,consul内部机制和分布式锁和选主实现的比较

    我的另外3篇文章分别介绍了Zookeeper,etcd,consul是如何实现分布式锁和选主的.本文想比较一下Zookeeper.etcd.consul内部机制有哪些不同,他们实现锁和选主的方式相同和 ...

  5. ZooKeeper入门(四)实现分布式锁

    首先是引入pom依赖: <dependency><groupId>com.101tec</groupId><artifactId>zkclient< ...

  6. ZooKeeper(四) 使用Redis RedissonLock 实现分布式锁

    本章将介绍使用RedissonLock 在SpringBoot 工程中实现分布式锁. 项目工程文档目录: │ pom.xml //maven 依赖文件 ├─src │ ├─main │ │ ├─jav ...

  7. Redis应用学习——Redis事务与实现分布式锁

    2019独角兽企业重金招聘Python工程师标准>>> 1. Redis事务机制 1. 与MySQL等关系数据库相同,Redis中也有事务机制,Redis的事务实质上是命令的集合,但 ...

  8. 【2020尚硅谷Java大厂面试题第三季 04】Redis 9种数据类型使用场景,分布式锁演变步骤,lua脚本,redis事务,Redisson,Redis内存占用,删除策略,内存淘汰策略,手写LRU

    1.安装redis6.0.8 2023 02 02 为:redis-7.0.8.tar.gz 2.redis传统五大数据类型的落地应用 3.知道分布式锁吗?有哪些实现方案?你谈谈对redis分布式锁的 ...

  9. ZooKeeper学习第五期--ZooKeeper管理分布式环境中的数据

    2019独角兽企业重金招聘Python工程师标准>>> 引言 本节本来是要介绍ZooKeeper的实现原理,但是ZooKeeper的原理比较复杂,它涉及到了paxos算法.Zab协议 ...

最新文章

  1. 计算机专业申请,申请计算机专业
  2. 使用resnet训练CIFAR10
  3. 【目录】C#操作Excel组件Spire.XLS系列文章目录
  4. python语言自学-【经验分享】自学Python的学习顺序!附学习资料
  5. 遇见那个对的人,便是爱情
  6. R语言实战应用精讲50篇(九)-正态分布的检验
  7. 区域数据导入功能(OCUpload插件使用)
  8. catia中sew的用法_CATIA超级副本(PowerCopy)使用方法总结 | 坐倚北风
  9. wifisetting.java_Wifi 笔记 | 启动流程
  10. 期待三分天下开源芯片有其一
  11. 如何做性能测试的一点思考总结
  12. java https请求_Spring Cloud Sleuth:分布式请求链路跟踪
  13. Android反编译方法
  14. 华为服务器报错信息,厂商 push 不通排查指南
  15. 想成为优秀的技术人员你必须做到的几件事情【转载】
  16. 基于智能手机传感器数据的人类行为识别
  17. ligerui combobox ajax,jquery ligerUI中ligerComboBox 初始值有关问题
  18. 计算机网络应用层和传输层及网络层协议有哪些?
  19. 计算机考研调剂心酸,考研,这一路走来、辛酸苦辣~百感交集~(转)
  20. YOLOX论文讲解和无人机检测项目实战

热门文章

  1. docker学习笔记2,入门到精通
  2. 【IoT】基于MCU实现NB-IoT设备对接移动oneNET平台
  3. php,验证身份证,年龄,归属地址,性别
  4. 蒙特卡洛树搜索的主要流程有哪些_蒙特卡洛树搜索简介与实现
  5. taro小程序跳转h5页面
  6. 基于滴滴云搭建 Lustre 分布式文件系统
  7. 简易Shader实践记录2-图片式心跳
  8. std在汇编语言是什么指令_汇编语言的各种指令的用法
  9. 问题 : 导弹防御系统
  10. 聊天机器人市场,主要划分为哪些区块,哪些企业需要使用聊天机器人?