画图说透 ZooKeeper如何保证数据一致性:选举和ZAB协议
1、zookeeper是什么?
zookeeper能被各个牛逼的中间件项目中所依赖,已经说明了他的地位。一出手就是稳定的杀招。zookeeper是什么?官网中所说,zookeeper致力于开发和维护成为一个高度可靠的分布式协调器。

开局一张图,官网就是酷,(图片来自官网)。
zookeeper能干的事情很多:
【分布式锁】,zookeeper特殊的数据结构和watcher机制,让他也能高效的实现分布式锁的功能,参考Curactor这款框架,分布式锁开箱即用。
【元数据管理】,Kafka就是使用zookeeper存储核心元数据。
【分布式协调】,zookeeper的watcher机制可以让分布式系统中的各个节点监听某个数据的变化,并且zookeeper可以把数据变化反向推送给订阅了的节点,例如kafka里面的各个broker和controller之间的协调。
【Master选举】,HDFS就是用了zookeeper来保证Namenode的 HA高可用,可以保证只有一台成为主。
zookeeper提供了全方位的分布式场景下丰富的功能,分布式协调的王者不是虚名。
zookeeper一般是集群部署,单机不可能满足上面提到的这些功能的实现。我们一般都是用3-5台机器。每台机器都会在内存存储所有的元数据。划重点,zookeeper是基于内存存储的,这已经决定了他能实现高吞吐高性能。
zookeeper的内存数据结构,就像linux系统的文件系统,是一种树形结构,每个节点我们叫做znode。zookeeper的节点大致分为三种类别,一个是临时节点,一个是持久节点,还有顺序节点。灵活运用,组合这些节点,我们在实际开发中能实现各种神奇的功能。

2.zookeeper的架构?
既然是分布式领域的一个王者,zookeeper具备哪些特性才让他立足呢?
- 集群部署,首先部署上肯定是集群部署,单机肯不靠谱的。
- 高可用,必须要保证高可用,一旦集群中的某台机器宕机,不能导致数据丢失
- 原子性,既然zookeeper走的CP,那么一定要保证每次数据写入,集群中所有机器必须要要么全部成功,要么就全部写入失败。
- 数据一致性,同(3)理,数据一致性也要保证,无论我客户端连接那一台机器,我get某个数据一定是一致的
- 顺序性,zookeeper的写入,实现了有序的写入,这也夯实了自己数据一致性的特性。
- 实时感知,zookeeper的watcher机制无疑是一个强大的功能,一旦某个数据发生变化,感知要实时,不然就失去意义了。
- 高性能,无论是做什么样的功能,超高的读写性能才是根本,zookeeper的数据是存储在内存中的,这本身就带来了超高的性能。并且同时也能带来超高的并发能力
要做到这些,架构设计上是绝对有很多亮点的,我们来看看zookeeper的架构设计。

首先我们先来认识一下zookeeper中的几个角色:
-
leader,集群启动,一定会选择一个节点作为主节点。一个集群中只有一个主节点,并且只有主节点接收并处理写请求。 -
follower,从节点,首先从节点是不能处理写请求的,就算某个客户端连接到从节点提交写请求,最终也是转发到主节点leader处理。从节点只负责读请求。当leader宕机的时候,并且当前集群中宕机的数量不超过一半,那么集群会重新发起选举,从follower中选举出新的leader。
Observer,顾名思义,观察者,一般我们接触zookeeper貌似都不怎么用到它,也注意不到他,如果你希望你的读并发能力能抗更多的请求,你可以选择挂载Observer。它只提供数据读取的服务。
当客户端和zookeeper建立起连接,是基于TCP的长连接,一个会话session,通过心跳机制,感知是否断开,如果断开,只要在sessionTimeout时间内重新连接,就能保持住长连接。
zookeeper的数据结构是znode树型结构,如上文一样,我们每次写入就是构建树节点下的叶子结点。并且创建的节点分为持久节点和临时节点。
- 持久节点一旦创建,会持久化到磁盘,哪怕客户端断开,下次依然可以读取到。
- 临时节点则不一样,一旦客户端断开连接的话就不存在了。和持久节点和临时节点能够组合的还有是否有序,在zookeeper的客户端curactor框架中就是基于临时顺序节点实现了分布式锁。
znode的结构长什么样呢?我们执行一次get操作,可以得到如下这样子的数据结构。

上文说过zookeeper一个很重要的功能是分布式协调,这就要依赖于zookeeper的监听回调机制,watcher机制,当客户端监听一个节点,当节点发生变化的时候反向通知客户端,如此便可以实现对订阅数据变化的感知,并做出响应的处理。基于TCP,天然可以实现这样的功能。

3.ZAB是什么?
Zookeeper一般都是集群部署,那么集群之间各个节点是如何同步数据的呢?如何才能保证数据对外的一致性呢?
这里就引出了Zookeeper的自己参照一致性协议paxos实现的,独有的ZAB协议,zookeeper Atomic Broadcast,zookeeper原子广播协议。正是通过这个协议,zookeeper的集群之间进行数据同步,保证数据的强一致性。
首先我们来拍脑袋想一想哪些情况下数据会不一致呢?正如上文提到的,所有的写请求是转发给leader处理的,再加上同步给follower,这中间网络通信,会出现各种情况,甚至leader崩溃,follower崩溃,这些都会导致数据不一致。
【那么ZAB协议是怎么起到保证一致性的作用呢? 】
-
首先明确的是整个zookeeper集群中的几个角色划分,只有leader负责写入数据,follower只负责从follower同步数据已经对外提供读取数据。可以说zookeeper实现的就是主备模型。
我们可以认为,一次写入就是一次分布式事务,从写入请求到leader,到follower的数据同步,到写入成功,返回客户端ACK。这就是分布式事务嘛。

-
当leader收到一条写入请求,这里我们称之为一次事务请求,就会将客户端事务请求转换成事务
Proposal(提议,提案),并且将这个事务Proposal发送给所有的Follower节点,也就是向所有的follower节点发送数据广播请求。 -
广播事务
Proposal之后leader就要等待所有的Follower服务器的返回,(ack请求),划重点,在ZAB协议中明确了只要有超过半数的Follower节点正确的返回了ACK,就认为本次提案是successful的。那么此时leader就会向所有的Follower服务器广播commit消息,对前一次的事务Proposal发起提交。这里我们敏锐的可以感觉到,脑海里浮现了一个词语 2PC。这个后面再说。
-
上面的过程我们可以理解为是一个2PC的过程,并且需要过半的机器成功接收到事务Proposal,并返回ACK,也就是过半写,2PC+过半写,这是ZAB协议的一个核心点。
我们再想想leader从哪里的呢?上面讨论的所有问题都是在唯一一台leader存在的情况下,那么leader是如何产生的?leader宕机了又该怎么办?这个是我们接下来要一点点分析的问题。
4.从启动到崩溃,ZAB协议做了什么?
我们来讨论leader从哪里来?既然是最有权势的一个leader,没有规矩,谁都能当?那肯定集群里面的节点都争着要当的,这时候我们就要自然是要选举出来的。怎么选,这也是ZAB协议里有规定的。
当一个ZK集群启动的时候,会进入崩溃恢复模式,直到选举出一个leader,并且只要过半的Follower机器都和leader机器同步完数据,就会退出崩溃恢复模式,对外提供服务,此时就进入了消息广播模式。
崩溃恢复模式和消息广播模式是zookeeper集群工作的主要俩个模式。
我们接着说leader选举的事情,ZAB协议规定leader选举只要过半的机器都投票给某一台机器,这台机器就成为leader,这里自己也可以投票给自
己。如果leader突然宕机了,此时整个集群再次进入崩溃恢复模式,重新选举新的leader。
消息广播模式就是集群数据副本传递的主要的策略,上文我们也说到了2PC,但其实zookeeper还是为了性能做了优化,并不是要所有的follower都
返回ACK,只要过半数返回ACK就认为本次事务Proposal是写入成功的,就可以发起commit请求了。
这里我们其实是有疑惑的,这样子,zookeeper还是强一致性么?毕竟在某个时间点,也不是所有的机器都拥有一致的副本啊,这么看来也不能说
Zookeeper是实现了强一致性的CP模型,这里有个结论,最终所有的机器会实现数据副本一致性。
消息广播模式下,每次从客户端有一个事务请求过来,leader会转化为事务Proposal,并且写入本地磁盘,还会给每个事务Proposal分配一个全局
唯一的Zxid。

还有个细节,当leader向所有的Follower发起事务Proposal时,不是就随随便便发送的,leader为每一个Follower都维护了一个先进先出的队
列,每一个事务Proposal都是按照顺序依次放入,保证有序性。当Follower接收到事务Proposal时,会将其写入本地磁盘。
这里还有异步解耦的功效,也一定程度上提高了事务Proposal的广播速度。那么这里我们还可以说zookeeper实现的是顺序一致性,这样看起来,更大程度的保证了数据的一致性。
5.Zxid的组成
Zxid其实就是事务id,为了保证事务的顺序一致性,zookeeper 采用了递增的事务id(Zxid)来标识事务。所有的提案(proposal)都在被提出的时候加上了 Zxid。
Zxid是64位的数字,高32位是任期编号,低32位是事务计数器:

- 图中高32位:
epoch,ZAB协议通过epoch编号来区分Leader周期变化,用来标识leader关系是否改变,每次新leader被选出来,所有节点的新epoch值为:(epoch初始值=1):new epoch值=old epoch值+1
可以理解成,标识当前集群属于哪个
leader的统治时期,保证了即使旧leader恢复后也没有人再从属于它(数据同步后就成了follower),因为follower只听从当前epoch的leader的命令。
- 图中低32位
transactionCount:用于递增计数,每接收到一条写入请求,这个值+1。新 leader选举后这个值重置为0。
这样设计的好处在于老的
leader挂了以后重启,它不会被选举为leader,因此此时它的zxid肯定小于当前新的leader。当老的leader作为follower接入新的leader后,新的leader会让它将所有的拥有旧的epoch号并且未被COMMIT的proposal清除掉。
6.数据不一致了,ZAB协议的策略
以上大概描述了ZAB协议下,zk集群中leader和follower之间数据副本的同步过程,我们在这里稍微消耗脑细胞地想一下,有没有什么情况,会导致数据不一致?
- 当一个客户端发过来的事务写请求,刚刚被leader写到本地,还没来得及发起
Proposal就宕机了,这时候会数据不一致么? 【P1】 - 如果一个事务
Proposal写入是成功的,并且广播给所有的Follower机器,也受到了过半机器的ACK,此时leader节点挂了,并且此时可能leader已经本地commit了,那么新的leader选举出来之后,这台老的leader重新启动了之后,数据会不一致么? 【P2】
细细一想,不经冷汗,这些也是问题啊,ZAB要保证自己说到的一致性就必须要解决这俩种情况,如何解决呢?
关键就在于新leader的选举算法,要保证2点:
- ZAB协议必须保证恢复处理后 【P2】 的需要在所有服务器上都提交成功
- ZAB必须将恢复后(此时已经有新leader了,已经跳过 【P1】 )重新注册的老leader机器所在的日志中删除掉 【P1】
上文所说的每个事务Proposal分配一个全局唯一的Zxid,并且是递增的。再加上任期的概念,一切又开始向着一致性的方向迈进了。
如果 leader 选举算法能够保证新选举出来的 leader服务器拥有集群中所有机器最高编号(【Zxid最大】)的事务 Proposal,那么就可以
保证这个新选举出来的 Leader 一定具有已经提交的提案。
毕竟所有提案被COMMIT之前必须有超过半数的 follower ACK,即必须有超过半数节点的服务器的事务日志上有该提案的 proposal,因此,只要有合法数量的节点正常工作,就必然有一个节点保 存了所有被 COMMIT 消息的 proposal 状态。而这个节点肯定会拥有最大的Zxid。
综上:
ZooKeeper主要依赖ZAB协议来实现分布式数据一致性,基于该协议,ZooKeeper实现了一种主备模式的系统架构来保持集群中各个副本之间的数据一致性。
相关文章:
画图说透 ZooKeeper如何保证数据一致性:选举和ZAB协议
1、zookeeper是什么? zookeeper能被各个牛逼的中间件项目中所依赖,已经说明了他的地位。一出手就是稳定的杀招。zookeeper是什么?官网中所说,zookeeper致力于开发和维护成为一个高度可靠的分布式协调器。 开局一张图,…...
错误异常捕获
1、React中错误异常捕获 在 React 中,可以通过 Error Boundaries(错误边界)来捕获错误异常。Error Boundaries 是一种 React 组件,它可以在其子组件树的渲染期间捕获 JavaScript 异常,并且可以渲染出备用 UI。React 提…...
js垃圾回收机制
内存的生命周期 ]S环境中分配的内存,一般有如下生命周期 1.内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存 2.内存使用:即读写内存,也就是使用变量、函数等 3.内存回收: 使用完毕,由垃圾回收器自动回收不再…...
YApi分析从NoSQL注入到RCE远程命令执行.md
0x00 前提 这个是前几个月的漏洞,之前爆出来发现没人分析就看了一下,也写了一片 Nosql注入的文章,最近生病在家,把这个写一半的完善一下发出来吧。 0x01 介绍 YApi是一个可本地部署的、打通前后端及QA的、可视化的接口管理平台…...
【C++】stl_list介绍和实现,list和vector区别,list vector string 迭代器失效
本篇博客详细介绍list的实现&细节讲解,并且在文章末对list和vector,string进行区分和复习 list的基本结构就是双向带头循环链表,链表和顺序表的差别我们在前面数据结构的时候早就学过了,不再赘述 在使用stl库里面list时&…...
linux-kernel-ecmp-ipv4
当使用ip route add/del添加或者删除路由时,通过触发netlink发送信息到各协议路由系统注册的netlink处理函数,如add时调用函数为inet_rtm_newroute。Equal Cost Multi Path,在ip交换网络中存在到达同一目的地址的多条不同的路径,而且每条路径…...
蒙特卡洛树搜索(MTCS)
一、目标 一种启发式的搜索算法,在搜索空间巨大的场景下比较有效 算法完成后得到一棵树,这棵树可以实现:给定一个游戏状态,直接选择最佳的下一步 二、算法四阶段 1、选择(Selection) 父节点选择UCB值最…...
【Verilog】——Verilog简介
目录 1.简介 2.什么是HDL以及HDL的功能 3.Verilog和C语言的比较 4.Verilog的用途 5.数字系统的抽象层次 1.系统级 2.算法级 3.RTL级(寄存器变换级) 6.数字系统抽象层级 7.自顶向下的结构化设计方法 8.Verilog建模 9.Verilog概述 10.Verilog模块的基本…...
【Python从入门到进阶】10、流程控制语句-循环语句(for-while)
接上篇《9、流程控制语句-条件语句(if-else)》 上一篇我们学习了Python的控制流语句的概念,以及其中的条件语句(if/else),本篇我们来学习控制流语句中的循环语句(for/while)。 一、Python中的循环 Python的循环结构就是让程序“杀个回马枪”࿰…...
超全的命令(代码)执行漏洞无回显的姿势总结(附带详细代码和测试分析过程)
目录 漏洞代码 突破方式 重定向 dnslog外部通信 burpsuite burpcollaborator外部通信 日志监听 netcat监听 反弹shell的各种姿势 漏洞代码 <?php shell_exec($_GET[a]); ?>这里使用了无回显的shell执行函数shell_exec,给html目录的权限是777 突破方…...
STM32MP157-Linux音频应用编程-简易语音助手
文章目录前言STM32MP157简易语音助手alsa-lib简介:移植alsa-lib库:libcurl库简介:移植libcurl库:API调用修改asrmain.c文件修改token.c文件录音文件IO打开音频文件硬件控制sysfs文件系统数据解析和控制多线程主循环实现效果及注意…...
Python-OpenCV图像处理:学习图像算术运算,如加减法、图像混合、按位运算,以及如何实现它们
目录 目标 图像添加 图像混合算法 按位运算 目标 学习对图像的几种算术运算,如加法、减法、位运算等。了解这些功能:cv.add()、...
并发编程——ReentrantLock
如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:耶瞳空间 一:基本介绍 从Java 5开始,引入了一个高级的处理并发的java.util.concurrent包,它提供了大量更高级的并发功能,能大大简化多线程程序的编写…...
English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六
English Learning - L2 第 3 次小组纠音 [ʌ] [ɒ] [ʊ] [ɪ] [ə] [e] 2023.3.4 周六共性问题小元音 [ʌ]小元音 [ɒ]小元音 [ʊ]小元音 [ɪ]小元音 [ə]小元音 [e]我的发音问题纠音过程共性问题 小元音 [ʌ] 口型容易偏大 解决办法:因为嘴角没有放松,…...
STM32之关门狗
看门狗介绍在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入…...
Apollo控制部分1-- ControlComponent组件介绍
Apollo控制部分1-- ControlComponent组件介绍摘要一、ControlComponent1、启动文件解析2、ControlComponent()组件函数解析1)ControlComponent::ControlComponent() 构造函数2)ControlComponent::Init() 初始化函数(执行一次)3&am…...
0626-0631韩顺平Java Buffered字节处理流 学习笔记
如何去构建字节流package com.hspedu.outputstream_;import java.io.*;/*** author abner* version 1.0*/ public class BufferedCopy02 {public static void main(String[] args) {String srcFilePath "D:\\Users\\Pictures\\Camera Roll\\Pierre-Auguste_Renoir,_Le_Mo…...
【网络】序列化和反序列化
🥁作者: 华丞臧. 📕专栏:【网络】 各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞收藏关注)。如果有错误的地方,欢迎在评论区指出。 推荐一款刷题网站 👉 LeetCode刷题网站 文章…...
【代码随想录训练营】【Day32】第八章|贪心算法|122.买卖股票的最佳时机II |55. 跳跃游戏|45.跳跃游戏II
买卖股票的最佳时机II 题目详细:LeetCode.122 买卖股票的最佳时机,怎么都能够想出来个思路,假如我们每天都能预知明天的股票是涨是降,那么贪心策略就是在涨之前买股票,在降的前一天卖掉,这就是买卖股票的…...
constexpr 和 常量表达式
👀👀常量表达式 常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。 字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。 那么是什么来就决定是不是常量表达式呢?一个对象是不是常量表达式主要…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
AirSim/Cosys-AirSim 游戏开发(四)外部固定位置监控相机
这个博客介绍了如何通过 settings.json 文件添加一个无人机外的 固定位置监控相机,因为在使用过程中发现 Airsim 对外部监控相机的描述模糊,而 Cosys-Airsim 在官方文档中没有提供外部监控相机设置,最后在源码示例中找到了,所以感…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
OD 算法题 B卷【正整数到Excel编号之间的转换】
文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的:a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...
