raft 线性一致性
什么是线性一致性
对于同一个对象 x,其初始值为 1,客户端 ABCD 并发地进行了请求,按照真实时间(real-time)顺序,各个事件的发生顺序如上图所示。对于任意一次请求都需要一段时间才能完成,例如 A,“x R() A” 到 “x Ok(1) A” 之间的那条线段就代表那次请求花费的时间。
四个次请求中只有 B 进行了写请求,改变了 x 的值,我们从 B 着手分析,明确 x 在各个时刻的值。由于不能确定 B 的 W(写操作)在哪个时刻发生,能确定的只有一个区间,因此可以引入上下限的概念。对于 x=1,它的上下限为开始到事件“x W(2) B”,在这个范围内所有的读操作必定读到 1。对于 x=2,它的上下限为 事件“x Ok() B” 到结束,在这个范围内所有的读操作必定读到 2。那么“x W(2) B”到“x Ok() B”这段范围,x 的值是什么?1 或者 2。由此可以将 x 分为三个阶段,各阶段"最新"的值如下图所示:
最后返回的 D 读到了 1,看起来是 “stale read”,其实并不是,它仍满足线性一致性。D 请求横跨了三个阶段,而读可能发生在任意时刻,所以 1 或 2 都行。同理,A 读到的值也可以是 2。C 就不太一样了,C 只有读到了 2 才能满足线性一致。因为 “x R() C” 发生在 “x Ok() B” 之后(happen before [3]),可以推出 R 发生在 W 之后,那么 R 一定得读到 W 完成之后的结果:2。
什么时候回复 client
raft 并不要求必须要等到 client 发来的 cmd 被 apply 到状态机后才回复 client,也就是说也可以在包含这个 cmd 的 raft log 被 commit 后立即回复client(如果 client 期望的 reply 仅仅是一个 true or false 代表这个 cmd 会不会最终被 apply,而不是期望得到一个状态机 apply 这条 cmd 之后产生的输出)。
但是在 raft 框架的实现中,往往都是等到 client 发来的 cmd 被 apply 到状态机后才回复 client。例如:
https://github.com/baidu/braft/blob/master/example/counter/server.cpp
void fetch_add(const FetchAddRequest* request,
CounterResponse* response,
google::protobuf::Closure* done) {
brpc::ClosureGuard done_guard(done);
...
butil::IOBuf log;
butil::IOBufAsZeroCopyOutputStream wrapper(&log);
if (!request->SerializeToZeroCopyStream(&wrapper)) {
...
}
// Apply this log as a braft::Task
braft::Task task;
task.data = &log;
// This callback would be iovoked when the task actually excuted or
// fail
task.done = new FetchAddClosure(this, request, response,
done_guard.release());