一致性算法
为什么需要一致性算法
- 分布式存储系统通常通过维护多个副本来提高系统的可用性和性能,要实现此目标,就必须维护多个副本的一致性
如何实现一致性算法
- 一致性协议通常基于Replicated state machines,即所有结点都从同一个state出发,都经过同样的一些操作序列(log),最后到达同样的state
Replicated state machines
【图1, 论文中的一致性状态机】
- 复制状态机通常都是基于复制日志实现的,如图 1。
- 每一个服务器存储一个包含一系列指令的日志,并 且按照日志的顺序进行执行。每一个日志都按照相同的顺序包含相同的指令,所以每一个服务器都执行 相同的指令序列
- 保证复制日志相同就是一致性算法的工作了
为什么需要用日志呢?
- 日志可以记录操作的顺序
- 让所有副本执行的操作的顺序相同
- 让leader可以确定所有follower的log是相同的
- log记录的是临时的记录,直到被commit
- log可以记录发送过的command,就是leader重复发送某个command,也没影响
- log的持久化,就算机器重启,也可以恢复之前的状态
所有副本见的日志都是一致的吗?
- 不一定,有些副本可能会延迟,但他们最终都会一致
- 一致性算法的规定,会执行那些被commit的command
raft
基础
-
【先上图2】
-
3种角色
- follower,candidate,leader
- 【论文图4】
-
2个Rpc
- requestVote
- appendEntry
-
raft的leader选举
-
raft如何保证日志的一致
Leader election
- 为什么需要leader?
- 确保所有副本按同样的顺序执行command
- 什么时候会发生选举
- 当follower,在一个超时时间内没收到leader的AppendEntry心跳,就会变成candidate,并发起投票RequestVote
- raft给leader进行term编号
- 一个leader对于一个term
- 通过term这个逻辑时间,可以让所有server之间比较,来找出最新的leader
- term是递增的,一个term里最多有一个leader
- 如何保证呢?
- 需要获得多数server的投票,才能成为leader
- 每个server在每个term,只能投一次票
- 即使发生网络分区,或部分机器(2n+1个机器, 少于n)fail了,也能完成选举
- 其他server如何感知新选举的leader呢?
- 新leader会 发送AppendEntries 心跳,给其他机器
- 这个心跳里带有 当前的,更高的逻辑时间 term
- 选举失败(获取少于多数的投票)后,会发送什么?
- 发起选举的机器,维护了一个election timeout的时间,超过这个时间没收到多数的投票,会再发起一次选举(term也更新)
- 如何避免 split vote
- 即所有机器同时发起选举–》同时选举失败–》同时超时–》再同时发起下次选举,,,,
- 每个server 维护了一个 随机的超时时间
- 这样就会有一个最快的server 完成选举,成为leader,发送 发送AppendEntries 心跳,给其他的server
- 如果发生了网络分区,旧的leader无法感知新leader,会怎么办?
- 能选出新leader,意味着获取了多数的投票
- 新leader有更高的term
- 旧leader的是更低的term,发起的appendEntry无法得到多数的返回
- 因此旧leader无法commit log,无法更新state,因此没有脑裂
Log Replication
- 通过appendEntry 的 来执行
- 先看一些流程
- client发送请求操作给leader
- leader append 自己的日志
- 给其它的server发AppendEntriesRPC
- follower写自己的日志,然后恢复leader
- leader等到了多数的回复后,标记这个日志是commited的了
- commited 意味着是可以被apply 到state machien的log
- 上面的多数,意味着,即使发生故障,下一个leader 也会有这个commited的log
- leader 的下一个appendEntry 会带有“已经commited”的log的编号,follower收到后,也会进行apply操作
Log Compaction
- 【论文图12】
- 因为log会很多,不能让日志无限增长。
- 整个系统进行snapshot来处理,snapshot之前的日志都可以丢弃
- 每个副本独立的对自己的系统状态进行Snapshot,并且只能对已经提交的日志记录(已经应用到状态机)进行snapshot。
集群成员变化
-
例如集群的集群数从5变为9
-
如果直接改,会不安全,【论文图10】
-
使用二阶段法
- 目前有很多种两阶段的实现。例如,有些系统在第一 阶段停掉旧的配置所以集群就不能处理客户端请求;然后在第二阶段在启用新的配置。
- 在 Raft 中,集 群先切换到一个过渡的配置,我们称之为共同一致;一旦共同一致已经被提交了,那么系统就切换到新的配置上
-
共同一致是老配置和新配置的结合:
- 日志条目被复制给集群中新、老配置的所有服务器。
- 新、旧配置的服务器都可以成为领导人。
- 达成一致(针对选举和提交)需要分别在两种配置上获得大多数的支持
-
共同一 致可以让集群在配置转换的过程中依然响应客户端的请求
如何优化只读的操作
- ReadIndex
- 可以通过readindex 实现 follower read
- follower 收到 read 请求之后,直接给 leader 发送一个获取 ReadIndex 的命令,leader 仍然走一遍之前的流程,然后将 ReadIndex 返回给 follower,follower 等到当前的状态机的 apply index 超过 ReadIndex 之后,就可以 read 然后将结果返回给 client 了。
- LeaseRead
- 相比readindex 无Heartbeat 的开销
https://pingcap.com/blog-cn/lease-read#page-content
https://pingcap.com/blog-cn/linearizability-and-raft#heading-4
一些用了raft的场景
- redis 的哨兵选主
- etcd
- tikv
资料
- 论文 https://pdos.csail.mit.edu/6.824/papers/raft-extended.pdf
- https://zhuanlan.zhihu.com/p/27207160
- http://www.kailing.pub/raft/index.html#home raft过程的动画