Linearizable Read通俗來講,就是讀請求需要讀到最新的已經commit的數據,不會讀到老數據。
對于使用raft協議來保證多副本強一致的系統(tǒng)中,讀寫請求都可以通過走一次raft協議來滿足。然后,現實系統(tǒng)中,讀請求通常會占很大比重,如果每次讀請求都要走一次raft落盤,性能可想而知。所以優(yōu)化讀性能至關重要。
從raft協議可知,leader擁有最新的狀態(tài),如果讀請求都走leader,那么leader可以直接返回結果給客戶端。然而,在出現網絡分區(qū)和時鐘快慢相差比較大的情況下,這有可能會返回老的數據,即stale read,這違反了Linearizable Read。例如,leader和其他followers之間出現網絡分區(qū),其他followers已經選出了新的leader,并且新的leader已經commit了一堆數據,然而由于不同機器的時鐘走的快慢不一,原來的leader可能并沒有發(fā)覺自己的lease過期,仍然認為自己還是合法的leader直接給客戶端返回結果,從而導致了stale read。
Raft作者提出了一種叫做ReadIndex的方案:
當leader接收到讀請求時,將當前commit index記錄下來,記作read index,在返回結果給客戶端之前,leader需要先確定自己到底還是不是真的leader,確定的方法就是給其他所有peers發(fā)送一次心跳,如果收到了多數派的響應,說明至少這個讀請求到達這個節(jié)點時,這個節(jié)點仍然是leader,這時只需要等到commit index被apply到狀態(tài)機后,即可返回結果。
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入口函數
網友評論