ZooKeeper的应用场景(分布式锁、分布式队列)

7 分布式锁

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

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

排他锁

排他锁(Exclusive Locks,简称X锁),又称为写锁或独占锁,是一种基本的锁类型。

如果事务T1对数据对象O1加上了排他锁,那么在整个加锁期间,只允许事务T1对O1进行读取和更新操作,其他任何事务都不能再对这个数据对象进行任何类型的操作——直到T1释放了排他锁。

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

下面我们就来看看如何借助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是一个临时节点,因此在以下两种情况下,都有可能释放锁。

(1)当前获取锁的客户端机器发生宕机,那么ZooKeeper上的这个临时节点就会被移除。

(2)正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除。

无论在什么情况下移除了lock 节点,ZooKeeper都会通知所有在/exclusive_lock节点上注册了子节点变更Watcher监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复“获取锁”过程。整个排他锁的获取和释放流程,可以用下图来表示。

共享锁

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

共享锁和排他锁最根本的区别在于,加上排他锁后,数据对象只对一个事务可见,而加上共享锁后,数据对所有事务都可见。下面我们就来看看如何借助ZooKeeper来实现共享锁。

定义锁

和排他锁一样,同样是通过ZooKeeper上的数据节点来表示一个锁,是一个类似于“/shared_lock/[Hostname]-请求类型-序号”的临时顺序节点,例如/shared_lock/192.168.0.1-0000000001,那么,这个节点就代表了一个共享锁。

获取锁

在需要获取共享锁时,所有客户端都会到/shared_lock这个节点下面创建一个临时顺序节点,如果当前是读请求,那么就创建例如/shared_lock/192. 168.0.1-0000000001的节点;如果是写请求,那么就创建例如/shared_lock/192.168.0. I-W-000000001的节点。

判断读写顺序

根据共享锁的定义,不同的事务都可以同时对同一个数据对象进行读取操作,而更新操作必须在当前没有任何事务进行读写操作的情况下进行。基于这个原则,我们来看看如何通过ZooKeeper的节点来确定分布式读写顺序,大致可以分为如下4个步骤。

(1)创建完节点后,获取/shared_lock节点下的所有子节点,并对该节点注册子节点变更的Watcher监听。

(2)确定自己的节点序号在所有子节点中的顺序。

(3)对于读请求:

如果没有比自己序号小的子节点,或是所有比自己序号小的子节点都是读请求,那么表明自己已经成功获取到了共享锁,同时开始执行读取逻辑。

如果比自己序号小的子节点中有写请求,那么就需要进入等待。

对于写请求:

如果自己不是序号最小的子节点,那么就需要进入等待。

(4)接收到Watcher通知后,重复步骤1。

释放锁

释放锁的逻辑和排他锁是一致的,这里不再赘述。整个共享锁的获取和释放流程,可以用下图来表示。

羊群效应

上面讲解的这个共享锁实现,大体上能够满足一般的分布式集群竞争锁的需求,并且性能都还可以一这里说的一般场景是指集群规模不是特别大,一般是在10台机器以内。

但是如果机器规模扩大之后,会有什么问题呢?我们着重来看上面“判断读写顺序”程的步骤3,结合下图给出的实例,看看实际运行中的情况。

针对上图中的实际情况,我们看看会发生什么事情。

(1)192.168.0.1这台机器首先进行读操作,完成读操作后将节点/192.168.0.1-R-0000000001删除。

(2)余下的4台机器均收到了这个节点被移除的通知,然后重新从/shared_lock节点上获取一份新的子节点列表。

(3)每个机器判断自己的读写顺序。其中192.168.0.2这台机器检测到自己已经是序号最小的机器了,于是开始进行写操作,而余下的其他机器发现没有轮到自己进行读取或更新操作,于是继续等待。

(4)继续....

上面这个过程就是共享锁在实际运行中最主要的步骤了,我们着重看下上面步骤3中提

到的:“而余下的其他机器发现没有轮到自己进行读取或更新操作,于是继续等待。”很

明显,我们看到,192.168.0.1 这个客户端在移除自己的共享锁后,ZooKeeper 发送了子

节点变更Watcher通知给所有机器,然而这个通知除了给192.168.0.2这台机器产生实际

影响外,对于余下的其他所有机器都没有任何作用。

相信读者也已经意识到了,在这整个分布式锁的竞争过程中,大量的“Watcher通知”和“子节点列表获取”两个操作重复运行,并且绝大多数的运行结果都是判断出自己并非是序号最小的节点,从而继续等待下一次通知——这个看起来显然不怎么科学。客户端无端地接收到过多和自己并不相关的事件通知,如果在集群规模比较大的情况下,不仅会对ZooKeeper服务器造成巨大的性能影响和网络冲击,更为严重的是,如果同一时间有多个节点对应的客户端完成事务或是事务中断引起节点消失,ZooKeeper服务器就会在短时间内向其余客户端发送大量的事件通知一这就是所谓的羊群效应。

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

改进后的分布式锁实现

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

(1)客户端调用create()方法创建一个类似于“/shared_ lock/[Hostname]请求类型-序号”的临时顺序节点。

(2)客户端调用getChildren()接口来获取所有已经创建的子节点列表,注意,这里不注册任何Watcher。

(3)如果无法获取共享锁,那么就调用exist()来对比自己小的那个节点注册Watcher。

注意,这里“比自己小的节点”只是一个笼统的说法,具体对于读请求和写请求不一样。

读请求:向比自己序号小的最后一个写请求节点注册Watcher监听。

写请求:向比自己序号小的最后一个节点注册Watcher 监听。

(4)等待Watcher通知,继续进入步骤2。

改进后的分布式锁流程如下图所示。

8 分布式队列

业界有不少分布式队列产品,不过绝大多数都是类似于ActiveMQ、Metamorphosis、Kafka和HornetQ等的消息中间件(或称为消息队列)。在本文中,我们主要介绍基于ZooKeeper实现的分布式队列。分布式队列,简单地讲分为两大类,一种是常规的先入先出队列,另一种则是要等到队列元素集聚之后才统一安排执行的Barrier模型。

FIFO:先入先出

FIFO (First Input First Output,先入先出)的算法思想,以其简单明了的特点,广泛应用于计算机科学的各个方面。而FIFO队列也是一种非常典型且应用广泛的按序执行的队列模型:先进入队列的请求操作先完成后,才会开始处理后面的请求。

使用ZooKeeper实现FIFO队列,和前面共享锁的实现非常类似。FIFO 队列就类似于一个全写的共享锁模型,大体的设计思路其实非常简单:所有客户端都会到/queue_fifo这个节点下面创建一个临时顺序节点,例如/queue. fifo/192. 168.0.1-0000000001。

创建完节点之后,根据如下4个步骤来确定执行顺序。

(1)通过调用getChildren()接口来获取/queue_fifo节点下的所有子节点,即获取队列中所有的元素。

(2)确定自己的节点序号在所有子节点中的顺序。

(3)如果自己不是序号最小的子节点,那么就需要进入等待,同时向比自己序号小的最后一个节点注册Watcher监听。

(4)接收到Watcher通知后,重复步骤1。

整个FIFO队列的工作流程,可以用下图来表示。

Barrier:分布式屏障

Barrier原意是指障碍物、屏障,而在分布式系统中,特指系统之间的一个协调条件,规定了一个队列的元素必须都集聚后才能统一进行安排, 否则一直等待。这往往出现在那些大规模分布式并行计算的应用场景上:最终的合并计算需要基于很多并行计算的子结果来进行。这些队列其实是在FIFO 队列的基础上进行了增强,大致的设计思想如下:开始时,/queue_barrier节点是一个已经存在的默认节点,并且将其节点的数据内容赋值为一个数字n来代表Barrier 值,例如n=10表示只有当/queue_barrier节点下的子节点个数达到10后,才会打开Barrier。之后,所有的客户端都会到/queue_barrier节点下创建一个临时节点,例如/queue_barrier/192.168.0.1。

创建完节点之后,根据如下5个步骤来确定执行顺序。

(1)通过调用getData()接口获取/queue_barrier节点的数据内容:10。

(2)通过调用getChildren()接口获取/queue_barrier节点下的所有子节点,即获取队列中的所有元素,同时注册对子节点列表变更的Watcher监听。

(3)统计子节点的个数。

(4)如果子节点个数还不足10个,那么就需要进入等待。

(5)接收到Watcher通知后,重复步骤2。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/78632.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Unity每日一记】计时器——各种方法的实现

👨‍💻个人主页:元宇宙-秩沅 👨‍💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍💻 本文由 秩沅 原创 👨‍💻 收录于专栏:uni…

(7)(7.3) 自动任务中的相机控制

文章目录 前言 7.3.1 概述 7.3.2 自动任务类型 7.3.3 创建合成图像 前言 本文介绍 ArduPilot 的相机和云台命令,并说明如何在 Mission Planner 中使用这些命令来定义相机勘测任务。这些说明假定已经连接并配置了相机触发器和云台(camera trigger and gimbal hav…

7.原 型

7.1原型 【例如】 另外- this指向: 构造函数和原型对象中的this都指向实例化的对象 7.2 constructor属性 每个原型对象里面都有个constructor属性( constructor构造函数) 作用:该属性指向该原型对象的构造函数 使用场景: 如果有多个对象的方法&#…

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集

文章目录 Ⅰ C part1 面向对象编程1 头文件与类的声明1.1 c vs cpp关于数据和函数1.2 头文件与类1.2.1 头文件1.2.2 class的声明1.2.3 模板初识 2 构造函数2.1 inline 函数2.2 访问级别2.3 ctor 构造函数2.3.1 ctor 的写法2.3.2 ctor/函数 重载2.3.3 ctor 放在 private 区 2.4 …

Vue3 —— watchEffect 高级侦听器

该文章是在学习 小满vue3 课程的随堂记录示例均采用 <script setup>&#xff0c;且包含 typescript 的基础用法 前言 Vue3 中新增了一种特殊的监听器 watchEffect&#xff0c;它的类型是&#xff1a; function watchEffect(effect: (onCleanup: OnCleanup) > void,o…

SpringBoot常用注解 - @Controller

Controller : Controller是加在类上面的注解&#xff0c;使得类里面的每个方法都返回一个视图页面 实际开发中&#xff0c;有时候只是让后端的结果返回到前端&#xff0c;而不作为新的视图页面&#xff0c;此时需要结合 ResponseBody&#xff0c;让这个方法返回给前端的不是一个…

搭建 Python 环境 | Python、PyCharm

计算机 计算机能完成的工作&#xff1a; 算术运算逻辑判断数据存储网络通信…更多的更复杂的任务 以下这些都可以称为 “计算机”&#xff1a; 一台计算机主要由以下这几个重要的组件构成 CPU 中央处理器&#xff1a;大脑&#xff0c;算术运算&#xff0c;逻辑判断 存储器&…

Nuxt3_1_路由+页面+组件+资源+样式 使用及实例

1、 简介 1.1 开发必备 node版本 v16.10.0 我使用的是16.14.0编辑器推荐使用Volar Extension 的VS code插件Terminal 运行nuxt指令 1.2 环境搭建 安装项目&#xff1a; npx nuxilatest init [first_nuxt3]进入项目目录&#xff1a; cd [first_nuxt3]安装依赖&#xff1a;n…

微型导轨怎么保养?

微型导轨一般都是用在一些小型的设备上面的&#xff0c;虽说微型导轨的尺寸非常小&#xff0c;但精度可一点都不低呢&#xff01;一般具体用在一些机械的取放臂上面&#xff0c;作为精密测量和检测&#xff0c;效果还是不错的。 微型导轨属于精密传动零件&#xff0c;我们在使用…

问道管理:旅游酒店板块逆市拉升,桂林旅游、华天酒店涨停

游览酒店板块14日盘中逆市拉升&#xff0c;到发稿&#xff0c;桂林游览、华天酒店涨停&#xff0c;张家界涨超8%&#xff0c;君亭酒店涨超5%&#xff0c;众信游览、云南游览涨逾4%。 音讯面上&#xff0c;8月10日&#xff0c;文旅部办公厅发布康复出境团队游览第三批名单&#…

仿牛客论坛项目day4|开发社区登录模块

1、发送邮件 使用spring-boot-starter-mail这个包 2、开发注册功能 &#xff08;1&#xff09;访问注册页面 功能拆解&#xff1a; 点击顶部的注册按钮&#xff0c;打开注册页面 新增文件&#xff1a;controller->login 具体实现过程&#xff1a; 增加一个getregist…

微信小程序 蓝牙设备连接,控制开关灯

1.前言 微信小程序中连接蓝牙设备&#xff0c;信息写入流程 1、检测当前使用设备&#xff08;如自己的手机&#xff09;是否支持蓝牙/蓝牙开启状态 wx:openBluetoothAdapter({}) 2、如蓝牙已开启状态&#xff0c;检查蓝牙适配器的状态 wx.getBluetoothAdapterState({}) 3、添加…

【先进PID控制算法(ADRC,TD,ESO)加入永磁同步电机发电控制仿真模型研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Kafka第三课

Flume 由三部分 Source Channel Sink 可以通过配置拦截器和Channel选择器,来实现对数据的分流, 可以通过对channel的2个存储容量的的设置,来实现对流速的控制 Kafka 同样由三大部分组成 生产者 服务器 消费者 生产者负责发送数据给服务器 服务器存储数据 消费者通过从服务器取…

负载均衡搭建

LVS-DR部署 [客户端] node1 192.168.157.148 [lvs] node2 192.168.157.142 [web服务器] node3 192.168.157.145 node4 192.168.157.146&#xff08;1&#xff09;[lvs] yum install -y ipvsadm.x86_64 配置LVS负载均衡服务 &#xff08;1&#xff09;手动添加LVS转发1&#xff…

Vue3 使用json编辑器

安装 npm install json-editor-vue3 main中引入 main.js 中加入下面代码 import "jsoneditor";不然会有报错&#xff0c;如jsoneditor does not provide an export named ‘default’。 图片信息来源-github 代码示例 <template><json-editor-vue class…

一个DW的计算

一个DW的计算 1- 题目: 已知一个DW1.1 要求: 从DW中取出指定的位的值1.1.1 分析1.1.2 实现1.1.3 简化实现1.1.4 验证 2- 题目: 已知一个DW2.1 要求: 从DW中的指定的P和S,取出指定的位的值2.1.1 分析2.1.2 实现 1- 题目: 已知一个DW 有图中所示一行信息&#xff0c;表示一个DW(…

【实用黑科技】如何 把b站的缓存视频弄到本地——数据恢复软件WinHex 和 音视频转码程序FFmpeg

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;效率…

uniapp编写微信小程序遇到的坑总结

1、阻止事件冒泡 使用uniapp开发微信小程序的时候&#xff0c;发现使用click.stop来阻止事件冒泡没有作用&#xff0c;点击了之后发现仍然会触发父组件或者祖先组件的事件。 在网上查阅&#xff0c;发现使用tap.stop才能阻止事件冒泡。 2、二维码生成 在网上找了很多&…

问题:【IntelliJ IDEA】解决idea自动声明变量加finall修饰符问题

问题:【IntelliJ IDEA】解决idea自动声明变量加finall修饰符问题 场景复现 1 new String() 2 快捷方式生成变量 final修饰的 final String s new String();步骤一&#xff1a;确保settings配置信息 settings-----》Editor------》Code Style--------》java下的这两个选项不…
最新文章