關於Paxos 幽靈復現問題的看法

2021-09-19 19:25:44 字數 3328 閱讀 6718

幽靈復現問題

來自郁白的部落格:

使用paxos協議處理日誌的備份與恢復,可以保證確認形成多數派的日誌不丟失,但是無法避免一種被稱為「幽靈復現」的現象,如下圖所示:

leaderab

c第一輪

a1-10

1-51-5

第二輪b

宕機1-6,20

1-6,20

第三輪a

1-20

1-20

1-20

第一輪中a被選為leader,寫下了1-10號日誌,其中1-5號日誌形成了多數派,並且已給客戶端應答,而對於6-10號日誌,客戶端超時未能得到應答。

第二輪,a宕機,b被選為leader,由於b和c的最大的logid都是5,因此b不會去重確認6-10號日誌,而是從6開始寫新的日誌,此時如果客戶端來查詢的話,是查詢不到6-10號日誌內容的,此後第二輪又寫入了6-20號日誌,但是只有6號和20號日誌在多數派上持久化成功。

第三輪,a又被選為leader,從多數派中可以得到最大logid為20,因此要將7-20號日誌執行重確認,其中就包括了a上的7-10號日誌,之後客戶端再來查詢的話,會發現上次查詢不到的7-10號日誌又像幽靈一樣重新出現了。

對於將paxos協議應用在資料庫日誌同步場景的情況,幽靈復現問題是不可接受,乙個簡單的例子就是轉賬場景,使用者轉賬時如果返回結果超時,那麼往往會查詢一下轉賬是否成功,來決定是否重試一下。如果第一次查詢轉賬結果時,發現未生效而重試,而轉賬事務日誌作為幽靈復現日誌重新出現的話,就造成了使用者重複轉賬。

為了處理「幽靈復現」問題,我們在每條日誌的內容中儲存乙個generateid,leader在生成這條日誌時以當前的leader proposalid作為generateid。按logid順序回放日誌時,因為leader在開始服務之前一定會寫一條startworking日誌,所以如果出現generateid相對前一條日誌變小的情況,說明這是一條「幽靈復現」日誌(它的generateid會小於startworking日誌),要忽略掉這條日誌。

第三態問題

第三態問題也是我們之前經常講的問題, 其實在網路系統裡面, 對於乙個請求都有三種返回結果

成功失敗

超時未知

前面兩種狀態由於服務端都有明確的返回結果, 所以非常好處理, 但是如果是第三種狀態的返回, 由於是超時狀態, 所以服務端可能對於這個命令是請求是執行成功, 也有可能是執行失敗的, 所以如果這個請求是乙個寫入操作, 那麼下一次的讀取請求可能讀到這個結果, 也可能讀到的結果是空的

就像在 raft phd 那個**裡面說的, 這個問題其實是和 raft/multi-paxos 協議無關的內容, 只要在分布式系統裡面都會存在這個問題, 所以大部分的解決方法是兩個

對於每乙個請求都加上乙個唯一的序列號的標識, 然後server的狀態機會記錄之前已經執行過序列號. 當乙個請求超時的時候, 預設的client 的邏輯會重試這個邏輯, 在收到重試的邏輯以後, 由於server 的狀態機記錄了之前已經執行過的序列號資訊, 因此不會再次執行這條指令, 而是直接返回給客戶端

由於上述方法需要在server 端維護序列號的資訊, 這個序列號是隨著請求的多少遞增的, 大小可想而知(當然也可以做一些只維護最近的多少條序列號個數的優化). 常見的工程實現是讓client 的操作是冪等的, 直接重試即可, 比如floyd 裡面的具體實現

那麼對應於raft 中的第三態問題是, 當最後log index 為4 的請求超時的時候, 狀態機**現的兩種場景都是可能的

所以下一次讀取的時候有可能讀到log index 4 的內容, 也有可能讀不到, 所以如果在發生了超時請求以後, 預設client 需要進行重試直到這個操作成功以後, 接下來才可以保證讀到的寫入結果. 這也是工程實現裡面常見的做法

對應於幽靈問題, 其實是由於6-10 的操作產生了超時操作, 由於產生了超時操作以後, client 並沒有對這些操作進行確認, 而是接下來去讀取這個結果, 那麼讀取不到這個裡面的內容, 由於後續的寫入和切主操作有重新能夠讀取到這個6-10 的內容了, 造成了幽靈復現, 導致這個問題的原因還是因為沒有進行對超時操作的重確認.

回到幽靈復現問題

那麼raft 有沒有可能出現這個幽靈復現問題呢?

其實在早期raft 沒有引入新的leader 需要寫入乙個包含自己的空的entry 的時候也一樣會出現這個問題

log index 4,5 客戶端超時未給使用者返回, 存在以下日誌場景

然後 (a) 節點宕機, 這個時候client 是查詢不到 log entry 4, 5 裡面的內容

在(b)或(c) 成為leader 期間, 沒有寫入任何內容, 然後(a) 又恢復, 並且又重新選主, 那麼就存在一下日誌, 這個時候client 再查詢就查詢到log entry 4,5 裡面的內容了

那麼raft 裡面加入了新leader 必須寫入一條當前term 的log entry 就可以解決這個問題, 其實和之前郁白提到的寫入乙個startworking 日誌是一樣的做法, 由於(b), (c) 有乙個term 3的日誌, 就算(a) 節點恢復過來, 也無法成了leader, 那麼後續的讀也就不會讀到log entry 4, 5 裡面的內容

那麼這個問題的本質是什麼呢?

其實這個問題的本質是對於一致性協議在recovery 的不同做法產生的. 關於一致性協議在不同階段的做法可以看這個文章

也就是說對於乙個在多副本裡面未達成一致的log entry, 在recovery 需要如何處理這一部分未達成一致的log entry.

對於這一部分log entry 其實可以是提交, 也可以是不提交, 因為會產生這樣的log entry, 一定是之前對於這個client 的請求超時返回了.

常見的multi-paxos 在對這一部分日誌進行重確認的時候, 預設是將這部分的內容提交的, 也就是通過重確認的過程預設去提交這些內容

而raft 的實現是預設對這部分的內容是不提交的, 也就是增加了乙個當前term 的空的entry, 來把之前leader 多餘的log 預設不提交了, 幽靈復現裡面其實也是通過增加乙個空的當前leader 的proposal id 來把之前的log entry 預設不提交

所以這個問題只是對於返回超時, 未達成一致的log entry 的不同的處理方法造成的.

在預設去提交這些日誌的場景, 在寫入超時以後讀取不到內容, 但是通過recovery 以後又能夠讀取到這個內容, 就產生了幽靈復現的問題

但是其實之所以會出現幽靈復現的問題是因為在有了乙個超時的第三態的請求以後, 在沒有處理好這個第三態請求之前, 出現成功和失敗都是有可能的.

所以本質是在multi-paxos 實現中, 在recovery 階段, 將未達成一致的log entry 提交造成的幽靈復現的問題, 本質是沒有處理好這個第三態的請求.

關於raft解決分布式一致性中的幽靈復現方案

分布式中 幽靈復現是指在不同的時間點讀取同乙份資料可能出現前後不一致的問題,其本質還是分布式一致性沒有得到保障 現象有三颱節點 a b c,初始日誌複製如下 數值表示term 數值下標表示index a 1 1 b 1 1 c 1 1 假設 a寫入日誌 a 1 1 1 三條term為1 index為...

關於Zookeeper的 Paxos演算法一致性協議

前言 paxos 一致性協議可以說是一致性協議研究的起點,也以難以理解聞名。其實協議本身並沒有多難理解,它的難理解性主要體現在 為何如此設計協議以及如何證明其正確性。本文嘗試通過流程圖來說明協議的內容以及基本應用過程,不涉及如何證明其正確性。基本概念 paxos 可以分為兩種 single decr...

關於不易復現的閃退(No Crash)

專案馬上要上架了,然而還有乙個時有時無的bug沒有解決。先說說這個不過的表現 閃退,而且沒有報異常 異常 獲了而已 當前的activity被 直接顯示上乙個沒有被 的activity。最後通過debug發現,是第三方的sdk mob簡訊驗證 報了乙個異常 併發修改 然而並不是sdk出了問題,而是我訪...