ceph儲存的monitor選舉流程

田润芦發表於2024-06-27

1. Monitor::init函式:(啟動函式)
啟動定時器,準備好之後 呼叫觸發函式bootstrap。每個monitor在初始化的時候都會根據它的IP地址被賦予一個rank值,當選舉leader時,rank值最小的monitor勝出當選leader。
rank值代表mon在monmap中的位置,-1代表不在monmap中。
2. bootstrap(觸發函式)
Monitor::bootstrap(),monitor程序的啟動邏輯,具體做了以下幾件事:
1.開始時設定monitor的狀態為STATA_PROBING
2.然後判斷是否設定了mon_compact_on_bootstrap引數,如果設定了,就執行compact操作,對monitor的store進行壓縮
3.如果叢集只有一個monitor,則該monitor直接勝出
4.根據mon_probe_timeout重置probe_timeout事件的時間
5.如果monitor在monmap中,則將其將如到outside_quorum集合中。
6.根據monmap,向其他peer一一傳送MMonProbe訊息。

  1. 呼叫wait_for_paxos_write,判斷paxos的狀態,如果poaxs正在寫或等待寫,則釋放mon鎖,等待寫完成了載加鎖。(結束之前io)
  2. 呼叫get_rank函式從monmap中得到自己的rank值。如果得到的新的rank值與舊的rank值不同,則通知所有的peer(同等級的mon)。給所有節點傳送MMonProbe::OP_PROBE
  3. 觸發選舉後傳送MMonProbe::OP_PROBE訊息,
    3. handle_probe(訊息處理函式)
  4. 接收所有訊息
  5. 呼叫Monitor::handle_probe_probe和Monitor::handle_probe_reply、還有handle_probe_stop、handle_probe_assert_stop.
    4. Monitor::handle_probe_probe
  6. 1.如果missing feature,傳送OP_MISSING_FEATURES的資訊,結束。
  7. 若message的傳送方的版本比自己新,無法透過paxos演算法部分做資料修復,需要重新bootstrap()從對方主動拉資料。(只要版本有更新,就要重新進入bootstarp函式)。
  8. 正常流程,彙報paxos狀態,last_commit,first_commit資訊,傳送OP_REPLY訊息。
  9. 如果發現了一個peer,那麼extra_probe_peers.insert(m->get_source_addr())

5. Monitor::handle_probe_reply()
Monitor::handle_probe_reply(),在這裡主要做了以下幾件事。
1.先判斷當前monitor所處的狀態如果是Probing或者Electing,則直接退出。
2.比對對方的monmap和自己monmap的epoch版本,如果自己的monmap版本低,則更新自己的map,然後重新進入bootstrap()階段。
3.如果當前Monitor處於synchronizing階段,則直接返回
4.比對彼此的paxos的版本,如果對方的paxos版本較低,否則判斷是否需要進行data的sync。這裡有兩種情況,如果自己的paxos版本是比對方的paxos_first_version紀錄的版本低,則會進行sync操作。如果自己paxos的版本和對方的版本相差太遠超過了設定的引數paxos_max_join_drift的值,也會先進行資料的sync而不會觸發重新的選舉操作(sync_start(other,false),false代表不選舉)。
5.如果從返回的訊息中判斷已經有一個quorum存在了,自己也在monmap中摒棄自己的ip地址不為空,則直接發起一個選舉。否則,會請求加入這個quorum。
6.如果沒有現成的quorum,並且自己在monmap中,則把peer新增到outside_quorum的集合中。如果此時outside_quorum中的成員大於等於monmap->size() / 2 + 1時,開始選舉,否則返回,等待條件滿足。
1. 比較epoch訊息和paxos和自己的是否一樣,保持每個mon一致
2. 之後if(m->quorum.size);當quorum.size不為0,則發起選舉(呼叫start_election);
3. 當quorum.size為0時,當發現自己處於monmap中,則把peer加入到outside_quorum中,當out_quorum.size>=need時(need=monmap/2+1),呼叫start_election開始選舉。
6. 開始選舉start_election函式

  1. 如果Paxos正在STATE_WRITING或者STATE_WRITING_PREVIOUS狀態,則等待paxos的更新完成。
  2. 重置monitor中的服務,包括probe timeout事件、停止時間檢查(mon time skew的檢查)、health檢查事件、scrub事件等,並且restart paxos以及所有的paxos service服務。
  3. 設定自己進入STATE_ELECTING狀態,並增加l_mon_num_elections和l_mon_election_call這些統計資料。
  4. 呼叫elector的call_election()。
    7. 開始選舉
  5. start_election函式中elector.call_election()呼叫start()
    從Mon->store中讀出mon的election_epoch儲存在epoch中,初始值為1,更新epoch的值使其變為奇數,表明進入了選舉cycle。epoch為偶數,表明已經形成了穩定的quorum。
    把自己加入到acked_me 集合中,並設定electing_me為true,希望大家選自己當leader。
    向monmap中的成員傳送MMonElection::OP_PROPOSE訊息。
    其它的Monitor收到訊息後,經過dispatch邏輯,即Monitor:: ms_dispatch() --> Monitor::_ms_dispatch() --> Monitor::dispatch_op()--> Elector::dispatch(),之後進入訊息處理流程。
    8. Elector::handle_propose(),首先確保收到訊息的epoch版本是處於選舉的版本(奇數)並且滿足對feature的要求。
    接著判斷將自己的選舉epoch設定為和訊息中包含的epoch的值。
    最後比對rank值,(主mon走)如果自己的rank值更小,且選舉是自己發起的,則自己不ack此次選舉;若不是自己發起的則是重新發起一輪選舉。(選舉必須是rank值最小的mon發起的)
    (備mon走)如果自己的rank值更大證明已經選出主,則備mon會進入Elector::defer()流程,傳送MMonElection::OP_ACK訊息,ack該輪選舉。
    9. 發起選舉的Monitor收到ACK訊息之後Elector::handle_ack(),進入處理流程:
    將ACK自己的peer加入到acked_me這個map中,如果acked_me的個數和monmap中成員的個數一樣,則表明選舉成功,進入victory流程。
    這裡有點需要搞清楚的是在有一個monitor down的情況下,剩餘的monitor是如何選舉成功的(acked_me的成員肯定和monmap的成員個數不相等)。
    10. Leader會進入Elector::victory()(主mon走),選舉成功函式,具體處理流程如下:
    將acked_me中的成員加入到quorum中,並且將election epoch的值加一使其變成偶數,標誌選舉過程結束。
    向quorum中的所有成員傳送MMonElection::OP_VICTORY,訊息通知大家選舉結束。
    告訴monitor自己選舉成功。

11. Leader進入Monitor::win_election(),具體處理流程如下:
設定自己的狀態為STATE_LEADER,清空outside_quorum中的成員。
呼叫paxos->leader_init()初始化paxos,以及所有的paxos_service服務。在paxos的初始化中會設定paxos的狀態為STATE_RECOVERING,並且呼叫Paxos::collect()函式,同步mon之間的資料,這個會在後面的Paxos資料更新部分介紹。
(paxos->leader_init()設定狀態STATE_RECOVERING重構、、Paxos::collect()函式,同步mon之間的資料)
啟動health_monitor服務,目前主要是檢查mon儲存空間的使用情況。
啟動timecheck檢查,確保monitor之間的時差不超過mon_clock_drift_allowed,如果超過就會報告mon clockskew。
更新monitor的metadata,其主要紀錄了以下資訊:

12. Peon在收到MMonElection::OP_VICTORY訊息之後進入Elector::handle_victory()(備mon走),具體處理流程如下:
將自己的election epoch設定成訊息中的epoch值。
進入Monitor::lose_election(),設定自己的狀態為STATE_PEON,呼叫peon_init初始化paxos以及相關的paxosservice,更新logger資訊。
取消自己的expire_event時間,即有引數mon_election_timeout控制的時間。

mon的啟動流程

  1. 初始化
    全部初始化,包括全域性變數、日誌模組啟動、日誌執行緒
  2. 啟動執行緒
    啟動service執行緒(負責重新開啟日誌檔案)
    啟動admin socke(獲取配置,修改配置)
  3. 構造物件
    構造mon db物件、構造Messenger繫結網路卡、構造Monitor物件
  4. 啟動
    1.Monitor預初始化(初始化paxos服務,註冊adminsocket命令)
    2.啟動messenger
    3.monitor初始化:啟動定時器,新增定時器事件,觸發選舉
    4.註冊訊號處理函式
    mon選舉流程
  5. 介紹monitor的一致性服務
    確保Monitor一致性服務的是Multi-Paxos,ceph中用Paxos來代替Multi-Paxos。
    Paxos節點與monitor節點繫結,每個mon啟動一個Paxos,Paxos為mon提供服務。其中一個Paxos節點作為leader其餘的為peon角色。Lerder可以發起議案,peon根據自己的本地歷史選擇接受或拒絕議案,並回復leader,leder提交超過半數Paxos節點接收的議案,這些Paxos節點被稱為quorum(法定人數)。
  6. Paxos節點的其他狀態:
  7. probing狀態(發現並更新節點資訊)
  8. electing狀態(用來選出leader,probing狀態受到過半數的認可後變為electing狀態)
  9. synchronizing狀態(probing發現資料差異過大時會進入synchronizing狀態進行同步)
  10. mon的選舉過程
    paxos演算法(少數服從多數)
  11. 每個節點可以作為提議者和接收者,提議者給超過半數的接收者傳送預提案。(預提案中有一個提案編號n)。
  12. 接收者接收到n後和之前接收到的其他預提案比較,如果n大於之前的提案編號,就反饋“接受”,(並且這個接收者不會再接受編號小於n的提案),並把自己之前接受過的編號最大的提案一併反饋回去。如果n小之前接受到的提案編號,就反饋“拒絕”,並一併反饋之前接受到的最大的提案編號。
  13. 如果提議者收到了大多數接收者的“接受”資訊後,那麼這個提議者的提議就被認可,它將傳送一個正式提案給所有節點,包括編號n和值v(v就是leader的編號,是提議者接收到反饋中編號最大的提案的v值,n是預提案編號),等待接收者接受。
  14. 接收者收到正式提案,接受這個提議,除非在這個過程中他接受到了更大的提案編號,並反饋了接受。

相關文章