訂閱
糾錯
加入自媒體

Generation Clock模式解決方案

2021-01-10 21:24
java達人
關注

作者: Unmesh Joshi

譯者: java達人

一個單調遞增的數(shù)字,表示服務器的generation 。

問題

在領導者和追隨者設置中,有可能會出現(xiàn)領導者與追隨者暫時斷開聯(lián)系的情況。leader進程中可能會出現(xiàn)垃圾收集暫停,或者暫時的網(wǎng)絡中斷,導致leader和follower之間的連接斷開。在這種情況下,領導者進程仍在運行,在暫;蚓W(wǎng)絡中斷結束后,它將嘗試向追隨者發(fā)送復制請求。這很危險,因為集群的其他部分可能已經(jīng)選擇了新的leader并接受了來自客戶機的請求。對于集群的其他部分來說,檢測來自舊leader的任何請求非常重要。舊的leader本身也應該能夠檢測到它暫時與集群斷開了連接,并采取必要的糾正措施放棄領導者身份。

解決方案

Generation Clock模式是Lamport時間戳的一個示例:這是一種簡單的技術,用于確定跨一組進程的事件順序,而不依賴于系統(tǒng)時鐘。每個進程維護一個整數(shù)計數(shù)器,該計數(shù)器在該進程執(zhí)行每個操作后遞增。每個進程還將這個整數(shù)連同進程交換的消息一起發(fā)送給其他進程。接收消息的進程通過獲取自己的計數(shù)器和消息的整數(shù)值之間的最大值來設置自己的整數(shù)計數(shù)器。這樣,任何進程都可以通過比較相關的整數(shù)來確定哪個操作在另一個操作之前發(fā)生。如果消息在多個進程之間交換,也可以對多個進程之間的操作進行比較。可以這樣比較的行為被稱為“因果關系”。

維護一個單調遞增的數(shù)字表示服務器的generation 。每次新領導者選舉時,都應該以增加一個generation 為標志。generation 需要在服務器重新啟動之后可用,因此它與 Write-Ahead Log中的每個條目一起存儲。正如在High-Water Mark中所討論的,追隨者使用此信息來查找日志中的沖突條目。

在啟動時,服務器從日志中讀取最近已知generation 。

class ReplicationModule…  this.replicationState = new ReplicationState(config, wal.getLastLogEntryGeneration());

有了領導者和追隨者,服務器就會在每次有新的領導者選舉時遞增generation 。

class ReplicationModule…  private void startLeaderElection() {      replicationState.setGeneration(replicationState.getGeneration() + 1);      registerSelfVote();      requestVoteFrom(followers);  }

服務器將生成的generation 作為投票請求的一部分發(fā)送到其他服務器。這樣,在一個成功的領導者選舉后,所有的服務器都有相同的generation。一旦領導被選出來,追隨者們就會被告知新generation 的情況

follower (class ReplicationModule...)  private void becomeFollower(Long generation) {      replicationState.setGeneration(generation);      transitionTo(ServerRole.FOLLOWING);  }

之后,領導者在發(fā)送給追隨者的每個請求中都包含了generation。它將generation包含在每個心跳消息以及發(fā)送給追隨者的復制請求中。

Leader在其Write-Ahead Log中保留每一個條目的generation

leader (class ReplicationModule...)  Long appendToLocalLog(byte[] data) {      var logEntryId = wal.getLastLogEntryId() + 1;      var logEntry = new WALEntry(logEntryId, data, EntryType.DATA, replicationState.getGeneration());      return wal.writeEntry(logEntry);  }

這樣,作為Leader和follower復制機制的一部分,它也被持久化到follower日志中

如果一個追隨者從一個被廢棄的領導者那里得到一個信息,這個追隨者可以辨別出來,因為它的generation 太小了。追隨者然后返回一個失敗的響應。

follower (class ReplicationModule...)  Long currentGeneration = replicationState.getGeneration();  if (currentGeneration > replicationRequest.getGeneration()) {      return new ReplicationResponse(FAILED, serverId(), currentGeneration, wal.getLastLogEntryId());  }

當一個領導者得到這樣一個失敗的響應時,他就變成了一個跟隨者,并期待來自新領導者的交流。

Old leader (class ReplicationModule...)  if (!response.isSucceeded()) {      stepDownIfHigherGenerationResponse(response);      return;  }  
private void stepDownIfHigherGenerationResponse(ReplicationResponse replicationResponse) {      if (replicationResponse.getGeneration() > replicationState.getGeneration()) {          becomeFollower(replicationResponse.getGeneration());      }  }

考慮下面的例子。在三個服務器集群中,leader1是現(xiàn)有的leader。集群中的所有服務器的generation都為1。Leader1向追隨者發(fā)送連續(xù)的心跳。Leader1有一個很長的垃圾收集暫停時間,比如5秒。追隨者沒有得到一個心跳,超時后選舉一個新的領導者。新領導者將generation增加到2。垃圾收集暫停結束后,leader1繼續(xù)向其他服務器發(fā)送請求。generation為2的追隨者和新領導者拒絕請求,并發(fā)送失敗響應,帶上generation2。leader1處理失敗響應,并回退做一個跟隨者,generation 更新為2。

例子

Raft Raft使用Term的概念來標記leader generation。

Zab 在Zookeeper中,epoch號作為每個事務id的一部分進行維護。因此,在Zookeeper中持久化的每個事務都有一個以epoch標記的generation。

Cassandra 在Cassandra中,每個服務器存儲一個generation編號,該編號在服務器每次重啟時遞增。generation信息保存在系統(tǒng)密鑰空間中,并作為gossip消息的一部分傳播到其他服務器。接收到gossip消息的服務器可以比較它所知道的generation值和gossip消息中的generation值。如果gossip消息中的generation值較高,則知道服務器已重啟,然后丟棄為該服務器維護的所有狀態(tài),并請求新的狀態(tài)。

Kafka中的Epoch 在Kafka中,每次為Kafka集群選擇一個新控制器時,都會創(chuàng)建一個epoch數(shù)并存儲在Zookeeper中。epoch包含在從控制器發(fā)送到集群中其他服務器的每個請求中。另一個稱為LeaderEpoch的epoch被維護,以了解追隨者分區(qū)High-Water Mark是否落后。

聲明: 本文由入駐維科號的作者撰寫,觀點僅代表作者本人,不代表OFweek立場。如有侵權或其他問題,請聯(lián)系舉報。

發(fā)表評論

0條評論,0人參與

請輸入評論內容...

請輸入評論/評論長度6~500個字

您提交的評論過于頻繁,請輸入驗證碼繼續(xù)

暫無評論

暫無評論

人工智能 獵頭職位 更多
掃碼關注公眾號
OFweek人工智能網(wǎng)
獲取更多精彩內容
文章糾錯
x
*文字標題:
*糾錯內容:
聯(lián)系郵箱:
*驗 證 碼:

粵公網(wǎng)安備 44030502002758號