Linearizable Read通俗来讲,就是读请求需要读到最新的已经commit的数据,不会读到老数据。

对于使用raft协议来保证多副本强一致的系统中,读写请求都可以通过走一次raft协议来满足。然后,现实系统中,读请求通常会占很大比重,如果每次读请求都要走一次raft落盘,性能可想而知。所以优化读性能至关重要。

从raft协议可知,leader拥有最新的状态,如果读请求都走leader,那么leader可以直接返回结果给客户端。然而,在出现网络分区和时钟快慢相差比较大的情况下,这有可能会返回老的数据,即stale read,这违反了Linearizable Read。例如,leader和其他followers之间出现网络分区,其他followers已经选出了新的leader,并且新的leader已经commit了一堆数据,然而由于不同机器的时钟走的快慢不一,原来的leader可能并没有发觉自己的lease过期,仍然认为自己还是合法的leader直接给客户端返回结果,从而导致了stale read。

Raft作者提出了一种叫做ReadIndex的方案:

当leader接收到读请求时,将当前commit index记录下来,记作read index,在返回结果给客户端之前,leader需要先确定自己到底还是不是真的leader,确定的方法就是给其他所有peers发送一次心跳,如果收到了多数派的响应,说明至少这个读请求到达这个节点时,这个节点仍然是leader,这时只需要等到commit index被apply到状态机后,即可返回结果。

func (n *node) ReadIndex(ctx context.Context, rctx []byte) error {    return n.step(ctx, pb.Message{Type: pb.MsgReadIndex, Entries: []pb.Entry{{Data: rctx}}})
}

处理读请求时,应用的goroutine会调用这个函数,其中rctx参数相当于读请求id,全局保证唯一。step会往recvc中塞进一个MsgReadIndex消息,而运行node入口函数

        		

网友评论