key/value service as the example, as in Lab 3
goal: 集群服务对客户端表现和单机一致
goal: 少数节点失效依旧可用
watch out for network partition and split brain!
系统层级 clients - k/v layer - k/v table - raft layer - raft log]
raft接口:client RPC -> Start() -> majority commit protocol -> applyCh
提示:
leader commits/executes 当集群中多数节点AppendEntries成功返回
leader 提交后通知集群提交和执行 (== send on applyCh)
为何不等所有节点回复?因为少数节点失效,仍要具备可用性
多数票方案为何有用?任何两个多数票选方案都一定存在重叠节点,保障了下一个leader可以看到所有commit过的log
节点crash后如何恢复?
raft允许少数节点crash后依旧可用,但是crash的节点要修复后加入集群,保证复制因子
两种策略:
第一,使用一个新的server加入,需要将全量log或者快照发送到该节点
第二,修复crash节点,重新加入,从持久化log中读取到log,然后慢慢从主节点catch up到最新的log
讨论哪些状态需要做持久化?
论文中给出,log[],votefor,currentTerm
因此上述状态的每次变更都需要持久化落盘操作
log[],需要记住log entry,来保证多数复制的log entry在选举中可以获胜,从而达到新leader必须有所有commit log的要求
voteFor,防止选举阶段发生两次投票事件造成双主
currentTerm,保障term单调递增,检测rpc消息中term是否是旧的
哪些状态不需要落盘?
commitIndex,lastApplied,next/matchIndex[],可以在重启后动态重建上述变量
持久化性能有影响?
SSD 0.1ms HD 10ms,因此持久化限制我们吞吐在 100-10000ops
RPC耗时,<<1ms
可以使用batch方式优化磁盘性能
service如何从crash中恢复?
最简单的方案,从初始态开始,重放持久化的raft日志,但是lastApplied没有持久化,因此需要额外的工作,同时回放所有日志对于长期运行的系统会很慢
使用snapshot技术,只回放近期的日志
we get as far as telling S1 and S4 that the new config is 1,2,4
S1: 1,2,3 1,2,4
S2: 1,2,3 1,2,3
S3: 1,2,3 1,2,3
S4: 1,2,4
OOPS! now *two* leaders could be elected!
S2 and S3 could elect S2
S1 and S4 could elect S1
Q: 何时主节点需要在读请求中commit no-op?
A: 两种方案保护client不会读到老的数据,leader刚刚选举完成时,不能确定commit位置,需要额外的操作,一种是commit no-op保障;另一种是租约机制。文中说读不会涉及任何write,只有一种场景除外,就是leader刚刚选举后的第一次read操作。
Q: commit no op保护的是什么?
A: 借助figure 8来阐述,依旧是c中的S1无法确定2是否已经commit,因此就无法知道返回给客户端的读是否需要包括2,所以它需要做一次commit,空的commit刚好适合这种场景,一旦commit一次后,2就会保证commit,后续的leader都会有2,否则会出现figure 8中的s5情况,2其实没有commit
Q: 租约机制如何实现,为何需要依赖bounded clock skew?
A: 有一种实现可以是,收到leader的心跳消息,保证接下来的100ms内不会产生选举,所以就不会出现figure 8中的情况,主节点可以安全的服务读请求100ms,然后每次心跳会续租。这需要每个节点对100ms的定义是一致的
Q: Cold和Cnew表示什么?是leader吗?
A: 表示的是集群的配置,主要是包含哪些节点,paper中没有提供更多的细节。可能就是ip地址加端口号,或者一个hostname
Q: 如何理解cold,cnew以及过度状态?
A: Cold_new的含义是,这个期间,多数票选需要保证节点both old and new。 Suppose C_old={S1,S2,S3} and C_new={S4,S5,S6}, S1为leader,最后阶段S1 commit cnew,但是它仍是leader,所以需要step down,让位给cnew中的节点做主节点
Q: 为什么cold需要关闭自己,否则就会导致选举?
A: 主要问题是cold节点没有commit cnew配置,所以这些节点没有获得集群变更给cnew的信息
Q: 切换配置时选主需要old new同时获得多数票选,会影响性能多少?
A: 基本不会,基于修改配置并不频繁,old new同时多数票选带来的负荷也不大,当集群中节点相对稳定的时候
Q: 新成员加入有一个过程没有投票权,这个过程如何提高了集群可用性?
A: 该改进主要是为了新的节点可以快速得到全量的log,防止其加入后无法commit log,因为一直在做catch up操作,导致集群不可用。如果节点没有cathup,那么coldnew就无法commit,直到它catch up并且提交了cold new才会继续执行client command,因为log是按顺序提交的,新的log就要等coldnew提交完成,因此该阶段可以减少集群停留在coldnew的时间,使得配置变更更迅速
Q: 没有投票权的server状态是否仅存在于配置变更中?还是说无论何时,只有catch up的server才能投票?如何定义catch up?
A: 论文中相关细节不多,可以猜测一下,情况应该是leader开启一次集群配置变更操作,应该是在所有新加入的节点都已经catchup后,当leader开始coldnew时,这些server就都已经catch up了,因为只有多数节点完成coldnew log的复制,才会commit coldnew,当cold newcommit时,集群中节点就具备投票权。
Q: 论文中采用一种方式抑制新一轮的选举,为了防止old集群节点timeout触发新的选举,为什么不通过消息的来源来判断发送消息的节点是否在集群中,这样就可以判断是否可以接收这条消息了?
A: 这种方法也可行,不过考虑一下,有一种情况是,在old new状态下,某个new的节点成为leader,此时少数old节点还没开始变更,它们仅知道old配置节点的存在,我们不希望这些节点忽略来自新leader的选举(不过忽略也没问题,因为多数节点已经coldnew了,否则不可能new节点成为主)
Q: 变更的起始点和终止点?
A: 当前leader察觉到coldnew log entry时开始,如果在cold new没有commit时切主,另一个主没有cold new,那么本次变更就提前结束了,变更失败但是一致性仍在是对的;继续会commit coldnew,commit cnew后,变更结束
Q: 如果state本身和log一样都很大,比如都是唯一的kv插入,快照还有用吗?
A: 取决于具体情况,对于kv而言,快照可以具备和log不同的结构,比如更快速的load到内存,而不是一条一条回放日志,因此依旧具备实用性,一般来说,log远比state大。
Q: 快照rpc会很消耗带宽资源吗?
A: 会的,优化起来并不容易,因为如果要增量复制,对state要求比较高,对哪些log可以安全删除要求也很高。
Q: 写快照耗时太长,会不会导致选举发生?
A: 有可能发生,快照Tb级别,会耗时s级别甚至更长,此时解决方案是后台写快照,可以采用copy-on-write方案,子进程写快照,或者保证快照每次写小于超时时间,然后分多次写也可以。
Q: figure 13是否少了一些条件判断,比如If lastIncludedIndex < commitIndex, return
immediately. or If there is already a snapshot and lastIncludedIndex < currentSnapshot.lastIncludedIndex, return immediately.
A: 是的,要防止接收陈旧的snapshot,由于网络原因后者乱序问题,可能发生这种情况
Q: 如果接收快照时,我的快照优先于请求来的如何处理?
A: 忽略请求即可,是安全的。
Q: 实际应用中如何处理快照问题?
A: TODO 参考etcd-raft实现
Q: RPCinstallSnapshot原子性?执行一半失败了怎么办?
A: 和其他rpc一样,务必保证原子性,要么成功要么失败,不能有中间状态。同时该rpc实现还需要保证幂等性,leader可能会重复发送请求。
Q: 压缩技术可否应用在快照上?
A: 可以的,取决于存储的数据类型。如果每一次快照和前一次都有很多冗余,可以使用tree结构,就可以在不同快照版本之间共享node。
Q: 执行op和将op写入log之间有什么关系?
A: 只有leader commit的log才能执行,leader首先执行,然后追随者根据请求中的commitindex,执行本地的log,执行的含义是将log entry给上层应用,对于kv服务来说,交给kv层来执行get或者put请求
Q: 如何实现确定当前主节点时存在的,从而忽略选主的消息?
A: 每个节点随机有一个选举的超时时间,当超时时间到达还没收到主的心跳或者其他的选举请求时,发起选举。举例来说,hb 10ms一次,leader 发送心跳,10,20,30,假设s1节点在30时没收到心跳,35时timeout到达,开始出发选举,s2在30收到了心跳,因此s2会至少保证到40之前,不会发生超时选举事件,这里的假设是30-40之间,主是活着的,当然也可以更长,允许到50,也就是允许断链一次心跳,此时s2收到s1的选举,可以安全的忽略