Zookeeper & Etcd 关键对比汇总
1. 选举
1.1 Etcd选举过程
每个节点启动时作为
Follwer
角色,经过一个ElectionTimeout
(启动时随时生成的默认150ms~300ms
)后进入Candidate
状态,并向其他节点发送消息MsgVote
消息(本节点的Term
、最新的日志Index
,最新日志的LogTerm
);其他节点收到投标后,进行两步判断:
- 预判断,:
r.Vote==m.Form
(是否已经投标给该节点)- 当前节点未投标且无leader节点;
- 对于预投标(
PreVote
),消息的任期比当前节点大;
- 任何一个条件判断则可以进入正式判断:
- 若消息中
日志Term
对当前节点大则投给消息发送方,若日志Term
相等,Index
比当前节点大则也将票投给消息发送方r.raftLog.isUpToDate(m.Index, m.LogTerm)
- 若消息中
- 若上面的判断失败,则返回拒绝
- 预判断,:
当发起投标方收到半数以上的头条,则转换自己的角色
becomeLader
(raft.go/stepCandidate
),并广播一条空消息给其他所有节点。- Candidate –> Leader
- Candidate –> Follower
广播时间 << 选举超时时间 << 平均故障间隔时间
1.2 Zookeeper选举过程
双向心跳,Leader
收不到半数以上的Follower
节点发来的心跳则发起选举;Follower
未收到Leader
心跳则发起选举,选举时状态切换成LOOKING
。
投票节点:首先将节点状态转变成
LOOKING
状态;投票节点:将自己作为
(SID、ZXID)
广播给其他节点;其他节点:接收其他节点的选举投票;
其他节点:如果接收的选举纪元大于本地的选举轮数则更新本地选举轮数,并将接收的投票跟本地投票比较后更新本地投票,再广播出去。否则执行4;
其他节点:判断外部投票是否优于本地,如果是则更新本地提议再广播出去;
比较纪元大小(最新日志记录的纪元,并不是当前选举纪元);
纪元相等的情况,比较
Zxid
;Zxid
相等的情况下 则比较Sid
;return ((newEpoch > curEpoch) ||
((newEpoch == curEpoch) && ((newZxid > curZxid) || ((newZxid == curZxid) && (newId > curId)))));
投票节点:收集投票提议,并进行统计,若有节点收集的投票大于一半以上则认为该节点为
Leader
节点;投票节点: 若投票选定节点是该节点则转变自己的服务器状态为
LEADING
,若不是则转换成FOLLOWING | OBSERVING
。
- LOOKING –> LEADING
- LOOKING –> FOLLOWING
2. 数据同步
2.1 Zookeeper数据同步
数据同步分为四种DIFF
、TRUNC
、TRUNC+DIFF
、SNAP
设 Learner节点最后处理的ZXID
为peerLastZxid
,Leader服务器提议缓存队列commitedLog
最小最大ZXID为minCommitedLog
、maxCommitedLog
;
- 若
peerLastZxid
小于minCommitedLog
,则执行SNAP
; - 若
peerLastZxid
在minCommitedLog
与maxCommitedLog
之间,则执行DIFF
; - 若
peerLastZxid
大于maxCommitedLog
时,则执行TRUNC
; - 若
peerLastZxid
在minCommitedLog
与maxCommitedLog
之间但又不在leader
中,则TRUNC+DIFF
。
2.2 Etcd数据同步
Etcd 中Leader
为每个从节点保存两个信息nextIndex
(下一次要复制的位置)、matchIndex
(已经匹配的位置)
当从
Candidate
角色转换成Leader
角色后,会将所有节点的nextIndex
改为当前节点最大的Index
,matchIndex
设置为 0,向所有节点发送Append Entries
信息;若接收到信息的节点的
commitedId
大于发送过来的nextIndex
,则告诉Leader
该索引号;若
msg.Index&Term
在节点日志中,则告知Leader
节点目前的mLastIndex
;若不匹配,则发送拒绝,并返回其日志中的
LastIndex
。若节点信息对不上
nextIndex
则返回日志失败,Leader
不断前移nextIndex
进行循环往复尝试,直至节点追加成功;
3. Watch机制
3.1 Zookeeper
Zookeeper
的watch
由引用层的WatchManager
进行管理,其中包含NodeWatcher
和ChildWatcher
:
- 当有数据更新时同时会调用
WatchManager
进行触发事件; - 从
WatcherManager
中筛选出符合条件的Watcher
触发下发,并把映射给摘除。- 只会触发一次,且不会把真正的数据下发下去而只下发
通知状态、事件类型、节点路径
,具体变更数据由Client
自行来查询; Watcher
事件传递到Client
触发后也会删除映射关系。
- 只会触发一次,且不会把真正的数据下发下去而只下发
因此存在丢失的可能:
- 在对于同一个key或者同一范围的监听,若修改速度迅速,会漏事件。
3.2 Etcd
Etcd
由两个协程和两个WatcherGroup
来管理Watcher
事件。详细见:Etcd Watch机制
- 带版本号监听;
- 连续监听不是单次监听。
4. 线性一致性
zab
&Raft
只是共识算法,并非线性一致性,实现线性一致性需要上层做出努力。如etcd
的线性一致性读。
什么是线性一致性读?
对于同一对象,读写请求按顺序线性进行。(读到最新写的数据)