此文章为博主在Bilibili上看完
(千锋教育)Java微服务架构Dubbo篇
之后所做的记录的
链接地址:(千锋教育)Java微服务架构Dubbo篇
原文档地址:原文档地址
Zookeeper简介
Zookeeper是一种分布式协调服务,用于管理大型主机。在分布式环境中协调和管理服务是一个复杂的过程。Zookeeper通过其简单的架构和API解决了这个问题。Zookeeper允许开发人员专注于核心应用程序逻辑,而不必担心应用程序的分布式特性
Zookeeper 的数据模型
- 树是由节点所组成,Zookeeper 的数据存储也同样是基于节点,这种节点叫做 Znode
但是,不同于树的节点,Znode 的引用方式是路径引用,类似于文件路径:
/动物/猫
/汽车/宝马
这样的层级结构,让每一个 Znode 节点拥有唯一的路径,就像命名空间一样对不同信息作出清晰的隔离。
Znode 包含哪些元素
- 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 列表。
- 当被 Watch 的 Znode 已删除,服务端会查找哈希表,找到该 Znode 对应的所有 Watcher,异步通知客户端,并且删除哈希表中对应的 Key-Value。
Zookeeper 的一致性
Zookeeper 身为分布式系统协调服务,如果自身挂了如何处理呢?为了防止单机挂掉的情况,Zookeeper 维护了一个集群。如下图:
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)。
接下来,节点会用自身的 ZXID 和从其他节点接收到的 ZXID 做比较,如果发现别人家的 ZXID 比自己大,也就是数据比自己新,那么就重新发起投票,投票给目前已知最大的 ZXID 所属节点。
每次投票后,服务器都会统计投票数量,判断是否有某个节点得到半数以上的投票。如果存在这样的节点,该节点将会成为准 Leader,状态变为 Leading。其他节点的状态变为 Following。
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
ZAB 协议既不是强一致性,也不是弱一致性,而是处于两者之间的单调一致性(顺序一致性)。它依靠事务 ID 和版本号,保证了数据的更新和读取是有序的。
-
ZAB写入过程
如何实现分布式锁
1.分布式锁
三个核心:
- 加锁
- 解锁
- 锁超时
三个问题:
要保证原子性操作,加锁和锁超时的操作要一次性执行
防止误删锁
在误删的基础上,加一个守护线程,为锁续命
2.Zookeeper分布式锁呢:
临时顺序节点:
- Znode有四种状态:
持久节点状态
持久节点有序节点状态
临时节点状态
临时节点有序节点
3.分布式协调
对临界资源的有序访问:
什么是临时顺序节点?
Zookeeper 的数据存储结构就像一棵树,这棵树由节点组成,这种节点叫做 Znode。
Znode 分为四种类型:
#持久节点(PERSISTENT)
默认的节点类型。创建节点的客户端与 Zookeeper 断开连接后,该节点依旧存在。
#持久节点顺序节点(PERSISTENT_SEQUENTIAL)
所谓顺序节点,就是在创建节点时,Zookeeper 根据创建的时间顺序给该节点名称进行编号:
#临时节点(EPHEMERAL)
和持久节点相反,当创建节点的客户端与 Zookeeper 断开连接后,临时节点会被删除:
#临时顺序节点(EPHEMERAL_SEQUENTIAL)
顾名思义,临时顺序节点结合和临时节点和顺序节点的特点:在创建节点时,Zookeeper 根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与 Zookeeper 断开连接后,临时节点会被删除。
#Zookeeper 分布式锁的原理
Zookeeper 分布式锁恰恰应用了临时顺序节点。具体如何实现呢?让我们来看一看详细步骤:
#获取锁
首先,在 Zookeeper 当中创建一个持久节点 ParentLock。当第一个客户端想要获得锁时,需要在 ParentLock 这个节点下面创建一个临时顺序节点 Lock1。
之后,Client1 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock1 是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。
这时候,如果再有一个客户端 Client2 前来获取锁,则在 ParentLock 下载再创建一个临时顺序节点 Lock2。
Client2 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock2 是不是顺序最靠前的一个,结果发现节点 Lock2 并不是最小的。
于是,Client2 向排序仅比它靠前的节点 Lock1 注册 Watcher,用于监听 Lock1 节点是否存在。这意味着 Client2 抢锁失败,进入了等待状态。
这时候,如果又有一个客户端 Client3 前来获取锁,则在 ParentLock 下载再创建一个临时顺序节点 Lock3。
Client3 查找 ParentLock 下面所有的临时顺序节点并排序,判断自己所创建的节点 Lock3 是不是顺序最靠前的一个,结果同样发现节点 Lock3 并不是最小的。
于是,Client3 向排序仅比它靠前的节点 Lock2 注册 Watcher,用于监听 Lock2 节点是否存在。这意味着 Client3 同样抢锁失败,进入了等待状态。
这样一来,Client1 得到了锁,Client2 监听了 Lock1,Client3 监听了 Lock2。这恰恰形成了一个等待队列,
#释放锁
释放锁分为两种情况:
#任务完成,客户端显示释放
当任务完成时,Client1 会显示调用删除节点 Lock1 的指令。
#任务执行过程中,客户端崩溃
获得锁的 Client1 在任务执行过程中,如果崩溃,则会断开与 Zookeeper 服务端的链接。根据临时节点的特性,相关联的节点 Lock1 会随之自动删除。
由于 Client2 一直监听着 Lock1 的存在状态,当 Lock1 节点被删除,Client2 会立刻收到通知。这时候 Client2 会再次查询 ParentLock 下面的所有节点,确认自己创建的节点 Lock2 是不是目前最小的节点。如果是最小,则 Client2 顺理成章获得了锁。
同理,如果 Client2 也因为任务完成或者节点崩溃而删除了节点 Lock2,那么 Client3 就会接到通知。
最终,Client3 成功得到了锁。
#Zookeeper 和 Redis 分布式锁的比较
文集推荐:
Java基础方法集1
Python基础知识完整版
Spring Boot学习笔记
Linux指令进阶
Java高并发编程
SpringMVC基础知识进阶
Mysql基础知识完整版
健康管理系统学习花絮(学习记录)
Node.js基础知识(随手笔记)
MongoDB基础知识
Dubbo学习笔记
Vue学习笔记(随手笔记)
声明:发表此文是出于传递更多信息之目的。若有来源标注错误或侵犯了您的合法权益,请作者持权属证明与本我们(QQ:981086665;邮箱:[email protected])联系联系,我们将及时更正、删除,谢谢。