容錯分散式一致性演算法——totem(一)
第一次接觸分散式一致性演算法是通過totem,現在開始研究paxos和raft之類,後面也是會做筆記的,便先將totem演算法先寫下來,用於對比學習。
totem協議,全稱是The Totem Single-Ring Ordering and Membership Protocol,是一個基於令牌環的分散式一致性演算法。一個令牌在叢集節點之間傳遞,拿到令牌的節點才能夠發訊息,訊息通過UDP廣播發送。所以,totem只適合在局域網裡的小叢集使用,這種情況下,這個演算法效能還算高的。從CAP來說,totem具有強一致性,但幾乎沒有分割槽容錯性,在網路出現分割槽時,totem會腦裂,當網路恢復時,會造成訊息丟失。所以我們叢集的做法是網路有問題時禁止節點發送訊息。
現在來具體介紹這個協議,首先我們明確totem實現了什麼:在多個節點組成的叢集中,totem實現讓一個節點發送訊息,其它所有節點都能全部收到,並且有序的提交給上層應用,這裡的環境和paxos一樣是除拜占庭問題外的環境。為了方便表述,且totem使用令牌環,這裡將一個叢集也稱作一個ring。下面是一張叢集的狀態圖,每個節點都會經歷以下狀態。
totem的節點有四個狀態,也是組建叢集的4個階段。
Gather階段:
這個階段用於每個節點向外界廣播自己的存在並收集其它節點的存在
Commit階段:
這個階段會產生一個代表節點,該節點向其它所有節點收集資訊,並將收集的資訊傳遞給其它所有節點,用於後續階段
Recovery階段:
這個階段用於新舊叢集交替時,舊叢集成員用新叢集傳遞舊叢集的訊息,使舊叢集成員達到所有節點訊息全部有序提交到上層
Operational階段:
這個階段是叢集組建完成正常工作的狀態,這個狀態一個節點發送的訊息其它節點都會全部有序提交給上層
totem協議可以概括為三個部分,叢集正常執行時候的THE TOTAL ORDERING PROTOCOL,operational狀態就是執行這個協議;叢集狀態變化時的THE MEMBERSHIP PROTOCOL,gather、commit和operational狀態包括在這個協議內;新舊叢集交替時,舊叢集訊息恢復的 THE RECOVERY PROTOCOL,recovery狀態就在這個協議內。
先講Operational狀態的THE TOTAL ORDERING PROTOCOL,這個是組成集群后的正常執行時的狀態,我們先設想它該具有怎樣的功能,再看totem協議有些什麼,這樣就很好理解totem協議的每個欄位和機制的作用了。首先,分發訊息的功能是一定有的,訊息是全部有序到達各個節點的,其次,作為一個強一致性的協議,節點fail或者網路出問題需要能夠感知。
看下需要每個processor本地維護的變數:
變數 | 值的意義 |
---|---|
my_token_seq | 本processor最後獲得token時的token_seq |
my_aru | 本processor收到my_aru序號以下的所有message |
my_aru_count | 本processor收到相同aru但aru不等於seq的token次數 |
new_message_queue | 本processor產生的等待發送的訊息佇列。 |
received_message_queue | 本processor收到的等待提交給application的訊息佇列 |
- my_token_seq這個變數用於辨認收到的token是否重複了,例如網路不好的時候,token會進行重傳。
- my_aru主要和token中的記錄的訊息seq對比,確定是否本節點有訊息沒收到。
- my_aru_count用於確定叢集是否發生異常,如果aru多次相同且不等於seq,則說明叢集網路出現問題或者叢集發生整體訊息丟失,會觸發重組叢集,後者一般不會出現。
- new_message_queue是個快取佇列,當拿到令牌的時候,節點順序傳送這些訊息。
- received_message_queue也是個快取佇列,節點收到的訊息先快取在這裡,當所有節點都收到連續到某個序號的訊息後(token裡的aru指示所有節點都收到的連續到的最大訊息號),會將這裡的訊息提交給上層,並刪除這些已提交的訊息。
有這些本地儲存的變數,現在再看下節點間傳些什麼
訊息型別1:Regular Message
變數 | 值的意義 |
---|---|
sender_id | 標記誰產生這個訊息 |
ring_id | 標記產生這個訊息的ring,包含ring sequence number和representative’s identifier。 |
seq | 訊息序號 |
conf_id | 0 |
contents | 要釋出到各個節點的內容 |
- sender_id用來記錄到底是哪個節點發出的訊息,但在corosync實現中似乎沒什麼用,除了排查bug會用下之外。
- rind_id用來標識ring,ring seq是順序遞增的,corosync的實現是每次+4, representative’s identifier是代表節點的標識,corosync的實現是代表節點ip。代表節點會在講解commit狀態的時候描述。
- seq是在此ring中訊息的序號,這個是叢集內唯一且順序遞增的。
- conf_id這個值在重組叢集的時候會用到,在totem演算法理解中不用太關注
- contents這個是訊息資料主體
訊息型別2:Regular Token
變數 | 值的意義 |
---|---|
type | Regular |
ring_id | token所屬的ring,包含ring sequence number和representative’s identifier |
token_seq | token序號 |
seq | ring裡訊息的最大序號 |
aru | (all-received-up-to) 所有processor都接收到小於或等於aru序號的訊息 |
aru-id | 記錄將aru設定成比seq小的processor |
rtr | 請求重新傳輸列表。 |
- type欄位用於標記這個token是什麼型別,主要有regular和commit型別,regular是operational和recovery狀態會有的,在commit階段的就是commit型別
- ring_id欄位和regular message的ring_id作用一致
- token_seq是令牌的序號標識,這個序號單調遞增,每經過一個節點增加1,用於認出重複的token
- seq記錄的是ring裡最大的regular message的序號,這個值用於標記令牌裡發的訊息的最大編號,在每個節點收到這個值後可以對比獲知自己缺了哪些訊息,每個節點在持有令牌的時候才發訊息,如果自己有發訊息,則傳遞令牌到下一個節點的時候會更新這個欄位。
- aru這個值用於各個節點獲知哪些訊息是所有節點都獲取到了的,當確認所有節點都獲取到訊息後,就開始提交到上層。當節點連續收到兩次token的aru都大於某值時,就認為該序號及以前的訊息被所有節點所接收。如果節點收到token,發現token裡的aru值大於自己已連續獲取的訊息序號最大值,則將token裡的aru修改成自己的aru值。並修改下面token裡的aru-id欄位,這樣可以告訴下一個獲取到token的節點自己缺了訊息。
- aru-id,節點在收到token時,如果自己已收到的包沒有達到token上記錄的seq那麼多(aru比seq小),則設定token的aru欄位並修改aru-id為本節點的識別符號。當本節點收到aru達到seq時,則判斷aru-id是否自己,如果是自己則清除這個值。
- rtr,當節點發現本節點收到的包比token上seq記錄的序號少時,就在token的rtr上記錄缺少的包。當token在其它節點迴圈時,其它節點如果發現本地有rtr上標記的包,就廣播該包,並清除rtr上的對於記錄,這樣就可以實現資訊共享了
從上述這些維護的變數以及訊息內容來說,可以基本的推斷totem協議在operational狀態是怎麼工作的了,下面具體看下協議內容:
協議在使用狀態是這樣的,令牌在每個節點迴圈,令牌是Regular Token描述的,節點拿到令牌之後才能傳送訊息,訊息是Regular Message結構體描述的。節點在拿到令牌後做這麼些事:
- 取消token 重傳定時器
- 檢視令牌rtr是否有訊息記錄,如果本節點有那些訊息則廣播這些訊息,並從rtr上刪除這些訊息
- 對比my_aru和令牌的seq,檢視是否有訊息本節點沒有收到,如果有則設定令牌上的aru和rtr以及aru_id
- 如果new_message_queue有訊息,則廣播訊息,並修改令牌中的seq
- 如果兩次token中的aru的值都大於某個值m,則向上提交序號大於m的訊息
- 傳送令牌給下一個節點
- 啟動token重傳定時器,再次收到token或者regular message的時候取消
token有重傳機制,用於防止訊息丟失和發現網路問題重組叢集,本地變數my_aru和token裡的aru和seq用於確認所有節點都收到訊息,aru_id和rtr用於重傳訊息給某節點。可以看到totem的每個變數設定目的都是很明確易懂的。至此,我想讀者應該明白totoem的operational狀態是怎麼運轉的了。
參考:
https://download.csdn.net/download/zancijun1666/10715783