raft算法和etcd代码解析-2.读写一致性
读写流程(etcd
graph TB
client(client/etcdCMD\n客户端) <-- request/response --> API(API\n接口)
API <--> log(WAL\n预写日志)
log -- input --> store(MVCC key value store\n多版本并发控制键值存储)
store -- output --> API
log <-- StorageInterface --> disk{Disk\n磁盘}
store <--> disk
log <--NetWorkTransport--> peer(RaftPeers\n其他raft节点)
[!quote] 写请求也会被写入预写日志吗?
etcd3.1之后,读请求不再写入预写日志,而是被阻塞等到所有写请求处理完之后响应读请求
[!quote] 每次提交entry都会写入磁盘吗?
不是每次提交entry都会调用磁盘的IO
日志状态
不同的预写日志状态被特定索引分割:
索引: applyIndex commitIndex stableIndex
↓ ↓ ↓
日志: 被应用的 可能回滚的 写入磁盘的 暂存的
一致性
leader将日志放到日志列表末尾
发送消息给follower,其中包括:日志和日志的任期索引,上一笔日志的任期索引
可能出现以下情况
- follower发现消息的任期号落后(这种情况表示出现了新的leader而旧的leader不知道),会响应旧的leader,告知其最新的任期号,旧的leader会成为follower。
- follower发现自己前一个任期号的索引超过消息的前一个任期号的索引(这种情况表示前一个任期的leader只同步日志还没提交数据就出于莫种原因下线了),follower会清除自己前面任期的多余数据
- follower发现自己索引落后,会拒绝同步,leader向其传送前面的日志
- 另一个leader收到了消息,此时任期落后的leader变为follower
如果超过半数follower认同这笔日志leader将commitIndex会向后移,向客户端告知更新成功(因此raft需要至少三个节点),状态机将会应用这个日志
leader的心跳消息会告知follower新的commitIndex,这样commitIndex左侧的日志一定是一致的,状态机读取commitIndex左侧的日志(apply),也就保证数据的一致了
状态机会应用commitIndex左侧的日志然后将applyIndex索引右移,然后反馈给客户端
客户端读取时会读取applyIndex对应的日志内容
心跳消息
心跳消息携带了索引的进度,也就是commitIndex,applyIndex所在位置信息。
心跳消息携带了任期号,leader接收到了另一个leader或candidate如果对方任期号更旧,leader告知对方任期已经更新。如果对方更新,则自己变为follower。
如果follower长期接收不到心跳就会重启发起选举
一致性模式
前面为默认的最终一致性,如果需要改成即时一致性,该怎么办改变源代码
- 给到客户端消息之前,立刻将数据应用到状态机,当然了,这会占用时间和性能
- 强制读主,只读leader的数据,因为leader数据总是最新的,并且这种情况下leader需要在收到亲求时需要发起广播,证明自己是正确的leader,这会增加leader节点负担
竞选流程
收到candidate信息先判断任期号再判断索引号,两个都比自己新的得到选票。
只要超过半数赞同响应就成为leader(包括自己的票数)。
leader如果收到其他candidate信息的任期号大于自己,立即成为follower。
竞选流程超时,candidate发起新的竞选流程。

浙公网安备 33010602011771号