Zookeeper 系列 - 我保证!样样聚到!没有一句废话,今日头条面试经历
[](
)四、ZK 和 Redis 两种分布式锁对比
===============================================================================
[](
)Redis 分布式锁
======================================================================
1. setnx + lua 脚本
优点:redis 基于内存,读写性能很高,因此基于 redis 的分布式锁效率比较高
缺点:分布式环境下可能会有节点数据同步问题,可靠性有一定的影响。比如现在有一个 3 主 3 丛的 Redis 集群, 客户端发生的命令写入了机器 1 的 master 节点,数据正准备主丛同步的时候,master 结点挂了,slave 结点没有接收到最新的数据,此时 slave 结点竞选为 master, 导致之前加的分布式锁失效。
2. Redission
优点:解决了 Redis 集群的同步可用性问题
缺点:网上是说:发布时间短,稳定性和可靠性有待验证。个人觉得,目前市面上已经稳定了,算是比较成熟的比较完善的分布式锁了。
[](
)ZK 分布式锁
===================================================================
优点:不存在 redis 的数据同步(zookeeper 是同步完以后才返回)、主从切换(zookeeper 主从切换的过程中服务是不可用的)的问题,可靠性很高
缺点:保证了可靠性的同时牺牲了一部分效率(但是依然很高)。性能不如 redis。
[](
)五、ZK 集群的 Leader 选举
============================================================================
Zookeeper 集群模式一共有 3 种类型的角色
Leader: 处理所有的事务请求(写请求),可以处理读请求,集群中只能有一个 Leader。
Follower:只能处理读请求,同时作为 Leader 的候选节点,即如果 Leader 宕机,Follower 节点要参与到新的 Leader 选举中,有可能成为新的 Leader 节点。
Observer:只能处理读请求。不能参与选举。
对于来自客户端的每个更新请求,ZooKeeper 都会分配一个全局唯一的递增编号。这个编号反映了所有事务操作的先后顺序,这个编号就
是 Zxid(ZooKeeper Transaction Id)。
[](
)Leader 选举流程
=======================================================================
ZK 内部采用一种快速选举算法,主要取决于两个因素(myid,Zxid), myid 是配置文件中维护的集群序列号,Zxid 为 ZK 的事务 id。
假设现在启动两台 ZK 集群,对应的 myid 分别为 1 和 2,那么在启动的过程中,就会发生 Leader 的选举,选举过程如下
由于项目刚启动,是没有任何事务的。所以,myid=1 的机器,投出去的票为(1,0),收到的票是(2,0),将收到的票跟自己投出去的票对比,优先原则 Zxid 大的为 Leader,因为 Zxid 越大则说明数据越新。如果 Zxid 一样,默认选择 myid 大的为 Leader, 则推荐(2,0)成为 Leader。
myid=2 的机器,投出去的票为(2,0), 收到的票是(1,0),按照上面的规则,选择(2,0)作为 Leader。
由于 ZK 的过半原则,当达到(集群个数/2+1)台机器选择同一个机器作为 Leader 时,改机器会从 Looking 状态切换到 Leader 状态,而其他的机器则会切换到 Follower 的状态。
[](
)为什么推荐 ZK 集群个数为奇数个?
=============================================================================
假设集群数量为 4 个,有一台 Leader 挂了,还剩 3 台 Follower, 由于 Leader 选举的过半原则,需要(3/2+1)=2 台 Follower 投票同一台机器才能成为 Leader。
假设集群数量为 3 个,有一台 Leader 挂了,还剩 2 台 Follower, 由于 Leader 选举的过半原则,需要(2/2+1)=2 台 Follower 投票同一台机器才能成为 Leader。
既然 3 台机器和 4 台机器的集群在 Leader 挂掉之后,都需要 2 台 Follower 投票同一台机器才能成为 Leader,那为什么不节省一台机器的成本呢?
所以,总得来说,就是为了节省成本。
[](
)Leader 选举多层队列架构
===========================================================================
整个 ZK 选举底层可以分为选举应用层和消息传输层
PS: 这里可能会看得有点晕, 先建立一个概念,ZK 在应用层和传输层都维护了队列和线程,我们下面会以应用层队列 和 传输层队列,应用层线程 和 传输层线程 区分开来。
应用层设计了一个统一发送投票的队列(应用层 SendQueue 队列)和接受投票的队列(应用层 ReceiveQueue 队列), 然后开启了一个应用层 WorkerSender 线程去扫描应用层 SendQueue 队列,开启一个应用层 WorkerReceiver 线程扫描传输层的传输层 ReceiveQueue 队列
传输层对 ZK 集群中除当前机器以外的机器都维护了一个发送队列,即每台机器对应一个发送队列,同时每个发送队列对应一个发送线程,这些线程不断的扫描属于自己的队列。
由于 ZK 集群的每台机器已经建立了 Soket 长链接,所以当发送线程扫描到新的投票消息时,会通过 Socket 发送给对应机器的传输层 ReceiveQueue 线程,然后传输层 ReceiveQueue 线程会把消息转存到统一的传输层 ReceiveQueue 队列中,当应用层 WorkerReceiver 线程扫描到传输层 ReceiveQueue 队列中的消息时,会把消息转发到应用层 ReceiveQueue 队列
最后统计投票,选举 Leader。
宝,是否有点疑问,ZK 为什么要这么做呢?
异步提升性能。
按发送的机器分了队列,避免给每台机器发送消息时相互影响,比如某台机器如果出问题发送不成功则不会影响对正常机器的消息发送。
[](
)五、ZK 的脑裂问题
======================================================================
脑裂通常会出现在集群环境中,比如 ElasticSearch、Zookeeper 集群,而这些集群环境有一个统一的特点,就是它们有一个大脑,比如 ElasticSearch 集群中有 Master 节点,Zookeeper 集群中有 Leader 节点。
[](
)什么是脑裂?
===================================================================
简单点来说,在正常的 ZK 集群中,只会有一个 Leader, 而这个 Leader 就是整个集群的大脑,脑裂,顾名思义,大脑分裂,即产生了多个 Leader。
[](
)ZK 中脑裂的场景说明
=======================================================================
对于一个集群,想要提高这个集群的可用性,通常会采用多机房部署,比如现在有一个由 6 台 ZK 所组成的一个集群,部署在了两个机房:
正常情况下,此集群只会有一个 Leader,那么如果机房之间的网络断了之后,两个机房内的 ZK 还是可以相互通信的,如果不考虑过半机制,那么就会出现每个机房内部都将选出一个 Leader。
这就相当于原本一个集群,被分成了两个集群,出现了两个"大脑",这就是所谓的"脑裂"现象。对于这种情况,其实也可以看出来,原本应该是统一的一个集群对外提供服务的,现在变成了两个集群同时对外提供服务,如果过了一会,断了的网络突然联通了,那么此时就会出现问题了,两个集群刚刚都对外提供服务了,数据该怎么合并,数据冲突怎么解决等等问题。
刚刚在说明脑裂场景时有一个前提条件就是没有考虑过半机制,所以实际上 Zookeeper 集群中是不会轻易出现脑裂问题的,原因在于过半机制。
[](
)ZK 过半机制为什么是大于,而不是大于等于?
==================================================================================
这就跟脑裂问题有关系了,比如回到上文出现脑裂问题的场景 :当机房中间的网络断掉之后,机房 1 内的三台服务器会进行领导者选举,但是此时过半机制的条件是 “节点数 > 3”,也就是说至少要 4 台 zkServer 才能选出来一个 Leader,所以对于机房 1 来说它不能选出一个 Leader,同样机房 2 也不能选出一个 Leader,这种情况下整个集群当机房间的网络断掉后,整个集群将没有 Leader。而如果过半机制的条件是 “节点数 >= 3”,那么机房 1 和机房 2 都会选出一个 Leader,这样就出现了脑裂。这就可以解释为什么过半机制中是大于而不是大于等于,目的就是为了防止脑裂。
如果假设我们现在只有 5 台机器,也部署在两个机房:
此时过半机制的条件是 “节点数 > 2”,也就是至少要 3 台服务器才能选出一个 Leader,此时机房件的网络断开了,对于机房 1 来说是没有影响的,Leader 依然还是 Leader,对于机房 2 来说是选不出来 Leader 的,此时整个集群中只有一个 Leader。因此总结得出,有了过半机制,对于一个 ZK 集群来说,要么没有 Leader,要么只有 1 个 Leader,这样 ZK 也就能避免了脑裂问题。
[](
)六、大名鼎鼎的 ZAB 协议
=========================================================================
[](
)什么是 ZAB 协议?
======================================================================
ZAB 协议是为 ZK 专门设计的一种支持崩溃恢复的一致性协议。基于该协议, ZK 实现了一种主从模式的系统架构来保持集群中各个副本之间的数据一致性。
在分布式系统中一般都要使用主从系统架构模型,指的是一台 Leader 服务器负责外部客户端的写请求。然后其他的都是 Follower 服务器。Leader 服务器将客户端的写操作数据同步到所有的 Follower 节点中。
像这样,客户端发送来的写请求,全部给 Leader,Leader 写完数据之后,再转给 Follower。这时候需要解决两个问题:
leader 服务器是如何把数据更新到所有的 Follower 的。
Leader 服务器突然间失效了,怎么办?
ZAB 协议为了解决上面两个问题,设计了两种模式:
消息广播模式:把数据更新到所有的 Follower
崩溃恢复模式:Leader 发生崩溃时,如何恢复
[](
)消息广播模式
===================================================================
ZAB 协议的消息广播过程使用的是一个原子广播协议,类似一个二阶段提交过程。对于客户端发送的写请求,全部由 Leader 接收,Leader 将请求封装成一个事务 Proposal,将其发送给所有 Follwer,然后,根据所有 Follwer 的反馈,如果超过半数成功响应,则执行 commit 操作。
Leader 将客户端的 Request 转化成一个 Proposal
Leader 为每一个 Follower 和 Observer 准备了一个 FIFO 队列,并把 Proposal 发送到队列上。
评论