Linearizable Read通俗來講,就是讀請求需要讀到最新的已經(jīng)commit的數(shù)據(jù),不會讀到老數(shù)據(jù)。

對于使用raft協(xié)議來保證多副本強一致的系統(tǒng)中,讀寫請求都可以通過走一次raft協(xié)議來滿足。然后,現(xiàn)實系統(tǒng)中,讀請求通常會占很大比重,如果每次讀請求都要走一次raft落盤,性能可想而知。所以優(yōu)化讀性能至關(guān)重要。

從raft協(xié)議可知,leader擁有最新的狀態(tài),如果讀請求都走leader,那么leader可以直接返回結(jié)果給客戶端。然而,在出現(xiàn)網(wǎng)絡(luò)分區(qū)和時鐘快慢相差比較大的情況下,這有可能會返回老的數(shù)據(jù),即stale read,這違反了Linearizable Read。例如,leader和其他followers之間出現(xiàn)網(wǎng)絡(luò)分區(qū),其他followers已經(jīng)選出了新的leader,并且新的leader已經(jīng)commit了一堆數(shù)據(jù),然而由于不同機器的時鐘走的快慢不一,原來的leader可能并沒有發(fā)覺自己的lease過期,仍然認(rèn)為自己還是合法的leader直接給客戶端返回結(jié)果,從而導(dǎo)致了stale read。

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

當(dāng)leader接收到讀請求時,將當(dāng)前commit index記錄下來,記作read index,在返回結(jié)果給客戶端之前,leader需要先確定自己到底還是不是真的leader,確定的方法就是給其他所有peers發(fā)送一次心跳,如果收到了多數(shù)派的響應(yīng),說明至少這個讀請求到達(dá)這個節(jié)點時,這個節(jié)點仍然是leader,這時只需要等到commit index被apply到狀態(tài)機后,即可返回結(jié)果。

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

處理讀請求時,應(yīng)用的goroutine會調(diào)用這個函數(shù),其中rctx參數(shù)相當(dāng)于讀請求id,全局保證唯一。step會往recvc中塞進(jìn)一個MsgReadIndex消息,而運行node入口函數(shù)

        		

網(wǎng)友評論