raft小论文, 18页:https://raft.github.io/raft.pdf
大论文,200+页:http://web.stanford.edu/~oust...

选举限制

为什么要添加election restriction?因为我们想要leader最终要包含所有已提交的日志.

假设没有这个限制,谁先timeout谁就选举,进而成为leader,那么会出现committed的日志被覆盖的情况。

相比于其他协议中先选举再补上日志的做法,raft则限制只有具有全部已提交的日志的candidate才可能赢得选举。具体做法是follower先检查最后一个日志的term,谁更大谁就更新; 如果一样,那么比谁更长。首先,谁最后的日志的Term更大,意味着该节点的逻辑时间更长,可能更新。其次最后一条日志的Term更大。

为什么么不能直接提交previous term的日志?

讨论基于上面的election restriction。
假设节点可以提交previous term的日志,我们来说明这种做法是不安全的。

来看看raft小论文的figure 8,假设到了c阶段,S1当前term为4。假设S1提交了黄色日志,此时S1突然crash了,S5成为了leader,S5会覆盖掉黄色的日志!也就是committed的日志又被覆盖了,违背了raft的保证。
但是如果不允许S1直接提交黄色,而是通过提交红色日志来间接提交黄色日志,那么此时即便S1 crash也不会让S5成为leader。
Leader推进其commitIndex的方法是counting replicas。这个通过提交当前term来间接提交之前term日志的限制,需要在counting 时只计算当前term的日志。

为什么leader需要提交一个no-op日志项

首先要明确的是,term为t2的leader怎么样确定一个term为t1的日志是committed的(t1可以等于t2)?需要同时满足下面两个条件:

  1. 日志t1至少被复制到Majority个节点上。
  2. leader至少提交了一条t2的日志。

第二条是因为leader不能直接认为复制到majority上的日志是committed,需要通过提交一条当前term(t2)的日志来间接提交t1.

考虑一个场景,leader L 提交了日志T,随后立刻挂掉了。新的Leader L2被选出来了,由于election restriction,它必然拥有T这一项,满足了第一个条件。那么它立即append一条当前Term(t2)的空白日志,那么就满足了第二个条件,促成日志L被提交。代价是日志中增加了一条啥内容也没有的空白日志。

如果不这样做,假设长时间没有新的client请求过来,日志L可能拖延很久才提交。

提高读吞吐

Raft中的写是线性一致性的,可以通过把读请求也写入日志来达到线性一致读,这种做法简单易行但是读吞吐会降到跟写吞吐一样的水平。
如何达到线性一只读同时提高系统的读吞吐?
大论文6.4节提供了一种做法,这种做法需要raft实现no-op的操作。步骤如下,假设此时客户端C的一条读请求到达了leader:

  1. leader至少提交了一条当前Term的日志(实现了no-op可以快速提交)。
  2. 记录readIndex=leader.commitIndex,readIndex代表一个下界,用来指示leader只少等到什么时候才能reply。
  3. 等待通过一轮心跳来确保当前leader没有过时。如果收到了Majority以上个ACK,那么表明leader没有过时。
  4. leader更新状态机,也就是要等到leader.lastApplied >= readIndex。
  5. leader读取结果,返回给client。

那么上述过程如何提高读吞吐?两者都需要等待一轮心跳,区别在于如果有k个并发读请求,读请求结果日志的方法会导致日志增加k项,增加了处理延迟(append,加锁解锁)和排队延迟(等待apply),而且增加了网络中的流量。

第二种方式中,如果leader能把批处理读请求,通过一轮心跳来完成第3步,那么k个读请求产生的流量只有O(n),与k无关,大大减少了流量。

这里笔者有个疑问:client绕过leader,直接读取Majority个节点,选取其中commitIndex最高的结果作为最终结果是否可行?

如果节点没有故障变更,那么这种办法是可行的(W+R>N).

但是这种方法有另外一个弊端: k个并发读请求,每个请求产生O(n)流量,合起来会产生O(nk)流量。

关于线性一致性

Raft可以实现线性一致性.什么是线性一致性?可以简单理解为:一系列请求[r1, r2, ..., rn]到达Raft系统,那么任意Raft副本执行的请求都是[r1, r2, ..., rn].
与线性一致性相似但是约束更弱的是顺序一致性,顺序一致性与线性一致性的相同在于任意副本都看到相同的序列,但这个序列不一定是[r1, r2, r3..., rn], 可以是[r2, r4, r1, ..., r5], 系统可以对这些指令进行重排.Raft天然便实现了顺序一致性.

Raft中的线性一致性:先后到达Raft系统的请求:A(read(x)), B(write(x, y)), C(read(x)), 它们各自的响应如下:

1 Reply-A -> x   // 初始值
2 Reply-B -> ok  // 修改
3 Reply-C -> y   // 请求C在B之后,要观察到B的修改结果

但有个微妙的地方:上面只规定了reply的内容应该满足上面要求,但是reply仍然需要经过不可靠的网络到达客户端,所以可能发生Reply-C先于Reply-A到达客户端,这种情况也满足线性一致性(这个图的client D)。

参考: https://pingcap.com/blog-cn/l...


Tsukami
9 声望9 粉丝

语雀: [链接]