背景
ZAB协议中主要有两种模式,第一是消息广播模式;第二是崩溃恢复模式
消息广播模式就是2pc,奔溃恢复模式就是在leader宕机如何重新同步数据
1
zookeeper(server + client )不是一个数据存储系统,不能存储大量的数据,它存储的是数据的状态,server端维护数据状态来保证client端实现 分布式锁、选举算法、分布式互斥等语义!
如何保证数据一致性,即多副本之间数据一致性,就成为server端必须解决的问题! 使用zab协议来实现
2
写到事务提交的时候,发现自己对分布式事务理解还不够深
之前的片面理解: 搜索广告点击产生分布式事务: 广告展示系统A + 广告扣费系统B 两个系统动作必须要同时成功或者回滚! 如果在同一个系统中,在一个事务中很好操作,但是现在是分布式系统了难度提高
现在的场景: zk leader 写请求 + zk follower 1 写同步 +.. , 这也是分布式系统,也需要保证同时成功或者失败!
所以都是分布式事务,所以用到的解决理论也是同一套: 2PC,3PC, TCC(try, commit, cancel)
正文
zk知识背景
a: 只有一个leader,多个follower,多个observer(只读),只有leader 能写,follow能提供读和选举投票
zk节点数要求是2f+1,在写或者选举的时候需要f+1个节点通过才能成功!
b: 只有leader能写,保证了其写的顺序性,因为只与一个节点通讯能保证接收的顺序
c: leader 奔溃,需要借助(myid, zxid) 比较大小来选举,最大的称为新的leader
d:zk 重读轻写场景
e:Zk规定节点的数据大小不能超过1M
Zab(Zookeeper Atomic Broadcast)
a:定义
Zxid: 在 ZAB 协议的事务编号 Zxid 设计中,Zxid 是一个 64 位的数字,其中低 32 位是一个简单的单调递增的计数器,针对客户端每一个事务请求,计数器加 1;而高 32 位则代表 Leader 周期 epoch 的编号,每个当选产生一个新的 Leader 服务器,就会从这个 Leader 服务器上取出其本地日志中最大事务的ZXID,并从中读取 epoch 值,然后加 1,以此作为新的 epoch,并将低 32 位从 0 开始计数。@hxx 每个follower只会接收比自己lastZxid 大的zxid的提议
epoch:可以理解为当前集群所处的年代或者周期,每个 leader 就像皇帝,都有自己的年号,所以每次改朝换代,leader 变更之后,都会在前一个年代的基础上加 1。这样就算旧的 leader 崩溃恢复之后,也没有人听他的了,因为 follower 只听从当前年代的 leader 的命令。@hxx 防止旧leader复活了,行使权力
选举: 每个follower都提供一个序列(myid, zxid)告诉集群自己要投这个节点称为leader,刚开始的时候当然都投自己啦, 然后每个节点都收到了其他节点发过来的二数据序列,然后与自己当前的(myid, zxid)比较
比较的过程是:先比zxid,找到最大的,然后再比myid找到最大的,这个时候得到一个新的序列,更新自己的序列,再次告诉集群,我这次投了新的这个序列(如果自己最大,因为上一次就是投的自己就不更新了保持)
最后统计票数,每个节点收到的投票结果,把超过半数的作为自己节点承认的leader(有可能某个节点没有统计的结果和别人有差别,那么它会连接到其实是一台follower的节点,但是连接的时候会遭到拒绝(因为那台follower知道自己不是leader,你不要连接我),认错leader的节点开始重新统计),只有当所有的节点都认为leader一致时,leader才会真正上位!
@hxx: 选zxid最大的是因为这个值最大说明这个节点的数据最新,可以减少主从数据同步;而myid最大没有特殊含义,因为myid是自己编号的,只是一种策略!
b: zab 奔溃恢复模式@hxx 当leader崩溃或者leader失去大多数的follower,这时zk进入恢复模式
step1: 选举, 上面讲了, 注意选出来的leader拥有最大的zxid!
step2: 恢复或称为发现, follower和leader 通讯,让leader知道自己有多少个follower,leader更新自己,并且为新leader生成新的epoch,让follower更新自己的acceptedEpoch@hxx 改朝换代了,老朝代的东西都要更新成新朝代的啦
step3:同步,follower接收leader的事务提议,当然follower只会更新比自己lastZxid的事务就好,或者进行事务的回退(如果在leader之后又接收了事务,这个时候不算)
step4: 广播, leader开始上朝了,zk集群正式对外提供事务服务,如果有新节点加入对新节点同步
注意,奔溃恢复模式需要保证两点:确保已经被leader提交的proposal必须最终被所有的follower服务器提交; 确保丢弃已经被leader提出的但是没有被提交的proposal。
c: zab 数据同步-事务提交
有一点不明,上面说了zk写请求,只会与leader通讯,那么直接写入leader就行呀,而且只要保证写入的顺序性就行使用TCP协议就可以做到这个顺序性,这里会有什么问题呢?
见前言
过程(2PC): client 写请求,follower将请求转发给leader(一个client只会连接一台server),leader收到请求,将提议发给所有follower,收到follower 写成功的ack超过半数,则再次像所有follower发送commit 命令,要求提交此次事务,否则cancel,leader将最新的数据同步给observer节点,follower将此次写请求结果返回给client!
@hxx 2pc 之前有笔记说过,缺陷是有一个协调中心(leader)单点,并且通信比较多,3pc是增加了通信超时策略
zookeeper中消息广播的具体步骤如下:
c.1. 客户端发起一个写操作请求
c.2. Leader服务器将客户端的request请求转化为事物proposql提案,同时为每个proposal分配一个全局唯一的ID,即ZXID。
c.3. leader服务器与每个follower之间都有一个队列,leader将消息发送到该队列
c.4. follower机器从队列中取出消息处理完(写入本地事物日志中)毕后,向leader服务器发送ACK确认。
c.5. leader服务器收到半数以上的follower的ACK后,即认为可以发送commit
c.6. leader向所有的follower服务器发送commit消息。
注意,zookeeper采用ZAB协议的核心就是只要有一台服务器提交了proposal,就要确保所有的服务器最终都能正确提交proposal。这也是CAP/BASE最终实现一致性的一个体现。
leader服务器与每个follower之间都有一个单独的队列进行收发消息,使用队列消息可以做到异步解耦。leader和follower之间只要往队列中发送了消息即可。如果使用同步方式容易引起阻塞。性能上要下降很多。@hxx 因为一个client只会连接到一个follower,所以不用担心队列间的数据顺序
d: observer 角色作用
在之前老的版本zk中没有observer,当client越来愈多时,需要对集群扩节点,那么扩的是follower节点,读性能提升了,但是写性能会严重下降
有了observer,扩节点时只扩observer节点,由于observer不参与投票,那么不会引起写性能的下降,所有observer出现是为了提高系统的弹性能力!
e:zookeeper部署模式
单机模式(一台zk),集群模式(3台或以上),伪集群(在一台机器上搭建多节点)
集群模式下的 observer模式
为了使用Observer模式,在任何想变成Observer模式的配置文件($ZOOKEEPER_HOME/conf/zoo.cfg)中加入如下配置:
peerType=observer
并在所有Server的配置文件($ZOOKEEPER_HOME/conf/zoo.cfg)中,配置成Observer模式的server的那行配置追加:observer,例如:
server.1:localhost:2181:3181:observer
领取专属 10元无门槛券
私享最新 技术干货