此文章为博主在Bilibili上看完
(千锋教育)Java微服务架构Dubbo篇
之后所做的记录的
链接地址:(千锋教育)Java微服务架构Dubbo篇
原文档地址:原文档地址
Zookeeper简介
Zookeeper是一种分布式协调服务,用于管理大型主机。在分布式环境中协调和管理服务是一个复杂的过程。Zookeeper通过其简单的架构和API解决了这个问题。Zookeeper允许开发人员专注于核心应用程序逻辑,而不必担心应用程序的分布式特性
Zookeeper 的数据模型
![](https://img.haomeiwen.com/i13687958/f7be1fe3e727999d.png)
- 树是由节点所组成,Zookeeper 的数据存储也同样是基于节点,这种节点叫做 Znode
但是,不同于树的节点,Znode 的引用方式是路径引用,类似于文件路径:
/动物/猫
/汽车/宝马
这样的层级结构,让每一个 Znode 节点拥有唯一的路径,就像命名空间一样对不同信息作出清晰的隔离。
Znode 包含哪些元素
![](https://img.haomeiwen.com/i13687958/002ad8328ee96283.png)
- data:Znode 存储的数据信息。
- ACL:记录 Znode 的访问权限,即哪些人或哪些 IP 可以访问本节点。
- stat:包含 Znode 的各种元数据,比如事务 ID、版本号、时间戳、大小等等。
- child:当前节点的子节点引用
这里需要注意一点,Zookeeper 是为读多写少的场景所设计。Znode 并不是用来存储大规模业务数据,而是用于存储少量的状态和配置信息,每个节点的数据最大不能超过 1MB。
Zookeeper 的基本操作
创建节点
create
删除节点
delete
判断节点是否存在
exists
获得一个节点的数据
getData
设置一个节点的数据
setData
获取节点下的所有子节点
getChildren
- 这其中,exists,getData,getChildren 属于读操作。Zookeeper 客户端在请求读操作的时候,可以选择是否设置 Watch
- Zookeeper
-------------------只要涉及到写操作,就会涉及到事务
Zookeeper 的事件通知
我们可以把 Watch 理解成是注册在特定 Znode 上的触发器。当这个 Znode 发生改变,也就是调用了 create
,delete
,setData
方法的时候,将会触发 Znode 上注册的对应事件,请求 Watch 的客户端会接收到异步通知。
具体交互过程如下:
- 客户端调用
getData
方法,watch
参数是true
。服务端接到请求,返回节点数据,并且在对应的哈希表里插入被 Watch 的 Znode 路径,以及 Watcher 列表。
![](https://img.haomeiwen.com/i13687958/4be88ade61c46ce6.png)
- 当被 Watch 的 Znode 已删除,服务端会查找哈希表,找到该 Znode 对应的所有 Watcher,异步通知客户端,并且删除哈希表中对应的 Key-Value。
![](https://img.haomeiwen.com/i13687958/1147cc4920b695f9.png)
Zookeeper 的一致性
Zookeeper 身为分布式系统协调服务,如果自身挂了如何处理呢?为了防止单机挂掉的情况,Zookeeper 维护了一个集群。如下图:
![](https://img.haomeiwen.com/i13687958/cf5419c6433007de.jpg)
Zookeeper Service 集群是一主多从结构。
在更新数据时,首先更新到主节点(这里的节点是指服务器,不是 Znode),再同步到从节点。
在读取数据时,直接读取任意从节点。
为了保证主从节点的数据一致性,Zookeeper 采用了 ZAB 协议,这种协议非常类似于一致性算法 Paxos和 Raft。
什么是 ZAB
Zookeeper Atomic Broadcast,有效解决了 Zookeeper 集群崩溃恢复,以及主从同步数据的问题。
ZAB 协议定义的三种节点状态
- Looking :选举状态。
- Following :Follower 节点(从节点)所处的状态。
- Leading :Leader 节点(主节点)所处状态。
#最大 ZXID
最大 ZXID 也就是节点本地的最新事务编号,包含 epoch 和计数两部分。epoch 是纪元的意思,相当于 Raft 算法选主时候的 term。
#ZAB 的崩溃恢复
假如 Zookeeper 当前的主节点挂掉了,集群会进行崩溃恢复。ZAB 的崩溃恢复分成三个阶段:
Leader election
选举阶段,此时集群中的节点处于 Looking 状态。它们会各自向其他节点发起投票,投票当中包含自己的服务器 ID 和最新事务 ID(ZXID)。
![](https://img.haomeiwen.com/i13687958/c4417968309745a3.png)
接下来,节点会用自身的 ZXID 和从其他节点接收到的 ZXID 做比较,如果发现别人家的 ZXID 比自己大,也就是数据比自己新,那么就重新发起投票,投票给目前已知最大的 ZXID 所属节点。
![](https://img.haomeiwen.com/i13687958/df1d42b62eec665f.png)
每次投票后,服务器都会统计投票数量,判断是否有某个节点得到半数以上的投票。如果存在这样的节点,该节点将会成为准 Leader,状态变为 Leading。其他节点的状态变为 Following。
![](https://img.haomeiwen.com/i13687958/fc6f9e6512f2e75e.png)
Discovery
发现阶段,用于在从节点中发现最新的 ZXID 和事务日志。或许有人会问:既然 Leader 被选为主节点,已经是集群里数据最新的了,为什么还要从节点中寻找最新事务呢?
这是为了防止某些意外情况,比如因网络原因在上一阶段产生多个 Leader 的情况。
所以这一阶段,Leader 集思广益,接收所有 Follower 发来各自的最新 epoch 值。Leader 从中选出最大的 epoch,基于此值加 1,生成新的 epoch 分发给各个 Follower。
各个 Follower 收到全新的 epoch 后,返回 ACK 给 Leader,带上各自最大的 ZXID 和历史事务日志。Leader 选出最大的 ZXID,并更新自身历史日志。
Synchronization
同步阶段,把 Leader 刚才收集得到的最新历史事务日志,同步给集群中所有的 Follower。只有当半数 Follower 同步成功,这个准 Leader 才能成为正式的 Leader。
自此,故障恢复正式完成。
#ZAB 的数据写入
Broadcast
ZAB 的数据写入涉及到 Broadcast 阶段,简单来说,就是 Zookeeper 常规情况下更新数据的时候,由 Leader 广播到所有的 Follower。其过程如下:
- 客户端发出写入数据请求给任意 Follower。
- Follower 把写入数据请求转发给 Leader。
- Leader 采用二阶段提交方式,先发送 Propose 广播给 Follower。
- Follower 接到 Propose 消息,写入日志成功后,返回 ACK 消息给 Leader。
- Leader 接到半数以上ACK消息,返回成功给客户端,并且广播 Commit 请求给 Follower
![](https://img.haomeiwen.com/i13687958/58b8764cd5e5705b.jpg)
ZAB 协议既不是强一致性,也不是弱一致性,而是处于两者之间的单调一致性(顺序一致性)。它依靠事务 ID 和版本号,保证了数据的更新和读取是有序的。
-
ZAB写入过程
ZAB写入过程.png
如何实现分布式锁
1.分布式锁
三个核心:
- 加锁
- 解锁
- 锁超时
三个问题:
要保证原子性操作,加锁和锁超时的操作要一次性执行
防止误删锁
在误删的基础上,加一个守护线程,为锁续命
2.Zookeeper分布式锁呢:
临时顺序节点:
- Znode有四种状态:
持久节点状态
持久节点有序节点状态
临时节点状态
临时节点有序节点
3.分布式协调
对临界资源的有序访问:
什么是临时顺序节点?
![](https://img.haomeiwen.com/i13687958/97f9cf77e82a3fec.png)
Zookeeper 的数据存储结构就像一棵树,这棵树由节点组成,这种节点叫做 Znode。
Znode 分为四种类型:
#持久节点(PERSISTENT)
默认的节点类型。创建节点的客户端与 Zookeeper 断开连接后,该节点依旧存在。
#持久节点顺序节点(PERSISTENT_SEQUENTIAL)
所谓顺序节点,就是在创建节点时,Zookeeper 根据创建的时间顺序给该节点名称进行编号:
![](https://img.haomeiwen.com/i13687958/24ee96e3135b9069.png)
#临时节点(EPHEMERAL)
和持久节点相反,当创建节点的客户端与 Zookeeper 断开连接后,临时节点会被删除:
![](https://img.haomeiwen.com/i13687958/e93ba64f24cd13fc.png)
![](https://img.haomeiwen.com/i13687958/18103fb49572341c.png)
![](https://img.haomeiwen.com/i13687958/0c04df281f3fbce9.png)
#临时顺序节点(EPHEMERAL_SEQUENTIAL)
顾名思义,临时顺序节点结合和临时节点和顺序节点的特点:在创建节点时,Zookeeper 根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与 Zookeeper 断开连接后,临时节点会被删除。
#Zookeeper 分布式锁的原理
Zookeeper 分布式锁恰恰应用了临时顺序节点。具体如何实现呢?让我们来看一看详细步骤:
#获取锁
首先,在 Zookeeper 当中创建一个持久节点 ParentLock。当第一个客户端想要获得锁时,需要在 ParentLock 这个节点下面创建一个临时顺序节点 Lock1。
![](https://img.haomeiwen.com/i13687958/82a0c8c8a239ebc3.png)
之后,Client1 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock1 是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。
![](https://img.haomeiwen.com/i13687958/74e926741bc84f19.png)
这时候,如果再有一个客户端 Client2 前来获取锁,则在 ParentLock 下载再创建一个临时顺序节点 Lock2。
![](https://img.haomeiwen.com/i13687958/3802f773d268c674.png)
Client2 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock2 是不是顺序最靠前的一个,结果发现节点 Lock2 并不是最小的。
于是,Client2 向排序仅比它靠前的节点 Lock1 注册 Watcher,用于监听 Lock1 节点是否存在。这意味着 Client2 抢锁失败,进入了等待状态。
![](https://img.haomeiwen.com/i13687958/93ccb3fc3339558e.png)
这时候,如果又有一个客户端 Client3 前来获取锁,则在 ParentLock 下载再创建一个临时顺序节点 Lock3。
![](https://img.haomeiwen.com/i13687958/d2e3b7838f3d2f7d.png)
Client3 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock3 是不是顺序最靠前的一个,结果同样发现节点 Lock3 并不是最小的。
于是,Client3 向排序仅比它靠前的节点 Lock2 注册 Watcher,用于监听 Lock2 节点是否存在。这意味着 Client3 同样抢锁失败,进入了等待状态。
![](https://img.haomeiwen.com/i13687958/38451c3a42289837.png)
这样一来,Client1 得到了锁,Client2 监听了 Lock1,Client3 监听了 Lock2。这恰恰形成了一个等待队列,
#释放锁
释放锁分为两种情况:
#任务完成,客户端显示释放
当任务完成时,Client1 会显示调用删除节点 Lock1 的指令。
![](https://img.haomeiwen.com/i13687958/3d6b0b057f857279.png)
#任务执行过程中,客户端崩溃
获得锁的 Client1 在任务执行过程中,如果崩溃,则会断开与 Zookeeper 服务端的链接。根据临时节点的特性,相关联的节点 Lock1 会随之自动删除。
![](https://img.haomeiwen.com/i13687958/0e031ea0e8c06d70.png)
由于 Client2 一直监听着 Lock1 的存在状态,当 Lock1 节点被删除,Client2 会立刻收到通知。这时候 Client2 会再次查询 ParentLock 下面的所有节点,确认自己创建的节点 Lock2 是不是目前最小的节点。如果是最小,则 Client2 顺理成章获得了锁。
![](https://img.haomeiwen.com/i13687958/124a234da9b01e5c.png)
同理,如果 Client2 也因为任务完成或者节点崩溃而删除了节点 Lock2,那么 Client3 就会接到通知。
![](https://img.haomeiwen.com/i13687958/182965d03b53cfc4.png)
最终,Client3 成功得到了锁。
![](https://img.haomeiwen.com/i13687958/3937480534685463.png)
#Zookeeper 和 Redis 分布式锁的比较
![](https://img.haomeiwen.com/i13687958/eb6083f9502896b0.jpg)
文集推荐:
Java基础方法集1
Python基础知识完整版
Spring Boot学习笔记
Linux指令进阶
Java高并发编程
SpringMVC基础知识进阶
Mysql基础知识完整版
健康管理系统学习花絮(学习记录)
Node.js基础知识(随手笔记)
MongoDB基础知识
Dubbo学习笔记
Vue学习笔记(随手笔记)
声明:发表此文是出于传递更多信息之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与本我们(QQ:981086665;邮箱:981086665@qq.com)联系联系,我们将及时更正、删除,谢谢。
网友评论