kafka如何做到高可用的

常常想如果讓你去設計一個高可用的系統,你怎麼去做?這裏要回答兩個問題:

  1. 如何保證宕機的時候數據不丟失? 答:副本
  2. 多副本之間數據如何同步? 答:同步;異步;半同步;ISR

這裏我們看一下kafka是怎麼設計做到高可用的,學習一下它:

如何保證宕機的時候數據不丟失?

對於每一個Topic,我們都可以設置它包含幾個Partition,每個Partition負責存儲這個Topic一部分的數據。然後Kafka的Broker集羣中,每臺機器上都存儲了一些Partition,也就存放了Topic的一部分數據,這樣就實現了Topic的數據分佈式存儲在一個 Broker 集羣上。但是有一個問題,萬一一個 Kafka Broker宕機了,此時上面存儲的數據不就丟失了嗎?沒錯,這就是一個比較大的問題了,分佈式系統的數據丟失問題,是它首先必須要解決的,一旦說任何一臺機器宕機,此時就會導致數據的丟失。

所以如果大家去分析任何一個分佈式系統的原理,比如說 Zookeeper、Kafka、RedisCluster、Elasticsearch、HDFS,等等。其實它們都有自己內部的一套多副本冗餘的機制,多副本冗餘幾乎是現在任何一個優秀的分佈式系統都一般要具備的功能。在Kafka集羣中,每個Partition都有多個副本,其中一個副本叫做Leader,其他的副本叫做Follower。

假設一個Topic拆分爲了3個Partition,分別是 Partition0,Partiton1,Partition2,此時每個Partition都有2個副本。比如 Partition0 有一個副本是 Leader,另外一個副本是Follower,Leader和Follower兩個副本是分佈在不同機器上的。這樣的多副本冗餘機制,可以保證任何一臺機器掛掉,都不會導致數據徹底丟失,因爲起碼還是有副本在別的機器上的。

爲了儘量做好負載均衡和容錯能力,需要將同一個Partition的Replica儘量分散到不同的機器。如果所有的Replica都在同一個Broker上,那一旦該Broker宕機,該Partition的所有Replica都無法工作,那麼這些Replica也就失去的意義。

多副本(replication)之間數據如何同步?

在看具體怎麼同步之前我們先需要了解幾個概念:

  • 我們創建topic的時候可以指定–replication-factor 3,表示分區的副本數(一般根據broker數量決定)
  • 我們說過leader是負責讀寫的節點,爲了保證較高的處理效率,消息的讀寫都是在固定的一個副本上完成。這個副本就是Leader,而其他副本則是Follower。無論副本有多少,Producer都只把消息發送到Leader上,Follower則會定期地到Leader上Pull數據。
  • ISR是leader負責維護的,與其保持同步的Replica列表,即當前活躍的副本列表。如果一個Follow落後太多,Leader會將它從ISR中移除,落後太多意思是該Follow複製的消息落後於Leader的條數超過預定值(參數:replica.lag.max.messages 默認值:4000)或者Follow長時間沒有向leader發送fetch請求(參數:replica.lag.time.max.ms 默認值:10000)
  • 爲了保證可靠性,我們通常設置ACK=all。Follower收到消息後,會像Leader發送ACK。一旦Leader收到了ISR中所有Replica的ACK,leader就commit,那麼Leader就像Producer發送ACK。

這些概念是高可用的基礎。

接着我們再看多個副本之間數據是如何同步的?其實任何一個 Partition,只有Leader是對外提供讀寫服務的。也就是說,如果有一個客戶端往一個 Partition 寫入數據,此時一般就是寫入這個Partition的Leader副本。然後Leader副本接收到數據之後,Follower副本會不停的給它發送請求嘗試去拉取Leader的數據,拉取到自己本地後,寫入磁盤中。

一般來說,對於這種情況有兩個處理方法

  • 同步複製,當producer向所有的Replica寫入成功消息後才返回。一致性得到保障,但是延遲太高,吞吐率降低。
  • 異步複製,所有的Replica選取一個一個leader,producer向leader寫入成功即返回,leader負責將消息同步給其他的所有Replica。但是消息同步一致性得不到保證,但是保證了快速的響應。

而kafka選取了一個折中的方式:ISR(in-sync replicas)。producer每次發送消息,將消息發送給leader,leader將消息同步給他“信任”的“小弟們”就算成功,巧妙的均衡了確保數據不丟失以及吞吐率。在所有的Replica中,leader會維護一個與其基本保持同步的Replica列表,該列表稱爲ISR(in-sync Replica),每個Partition都會有一個ISR,而且是由leader動態維護。如果一個replica落後leader太多,leader會將其剔除。如果另外的replica跟上腳步,leader會將其加入。

  • 同步:leader向ISR中的所有replica同步消息,當收到所有ISR中replica的ack之後,leader才commit。
  • 異步:收到同步消息的ISR中的replica,異步將消息同步給ISR集合外的replica。

如何Leader的選舉?

瞭解了Kafka如何做Replication,隨之而來的疑問便是如何選取leader?leader選舉可謂是一個經典問題,立馬想到了paxos,raft、Zab等算法,然而kafka採用的方法相比就簡單很多:Kafka的Leader選舉是通過在zookeeper上創建/controller臨時節點來實現leader選舉,並在該節點中寫入當前broker的元信息。一個節點只能被一個客戶端創建成功,創建成功的broker即爲leader,即先到先得原則。當leader和zookeeper失去連接時,臨時節點會刪除,而其他broker會監聽該節點的變化,當節點刪除時,其他broker會收到事件通知,重新發起leader選舉。另外兩種方式對比

1.基於Zookeeper的選舉方式

大數據很多組件都有Leader選舉的概念,如HBASE等。它們大都基於ZK進行選舉,所有Follow都在ZK上面註冊一個Watch,一旦Leader宕機,Leader對於的Znode會自動刪除,那些Follow由於在Leader節點上註冊了Watcher,故可以得到通知,就去參與下一輪選舉,嘗試去創建該節點,zK會保證只有一個Follow創建成功,成爲新的Leader。
但是這種方式有幾個缺點:

  • split-brain:這是由ZooKeeper的特性引起的,雖然ZooKeeper能保證所有Watch按順序觸發,但並不能保證同一時刻所有Replica“看”到的狀態是一樣的,這就可能造成不同Replica的響應不一致
  • herd effect 如果宕機的那個Broker上的Partition比較多,會造成多個Watch被觸發,造成集羣內大量的調整
  • ZooKeeper負載過重 每個Replica都要爲此在ZooKeeper上註冊一個Watch,當集羣規模增加到幾千個Partition時ZooKeeper負載會過重。

2. 基於Controller的選舉方式

Kafka 0.8後的Leader Election方案解決了上述問題,它在所有broker中選出一個controller,所有Partition的Leader選舉都由controller決定。controller會將Leader的改變直接通過RPC的方式(比ZooKeeper Queue的方式更高效)通知需爲爲此作爲響應的Broker。同時controller也負責增刪Topic以及Replica的重新分配。
優點:極大緩解了HerdEffect問題、減輕了ZK的負載,Controller與Leader\Follower之間通過RPC通信,高效且實時。缺點:引入Controller增加了複雜度,且需要考慮Controller的Failover

如果Replica都死了怎麼辦?

我們知道,只要至少有一個replica,就能保證數據不丟失,可是如果某個partition的所有replica都死了怎麼辦?有兩種方案:

  • 等待在ISR中的副本起死回生並選擇該副本作爲leader
  • 選擇第一個活過來的副本 (不一定在 ISR中),作爲leader。

這就需要在可用性和一致性當中做個折衷。如果一定要等待副本起死回生,那麼等待的時間可能比較長,而且如果都沒辦法活過來,那麼pertition將永遠不可用,這樣降低了可用性。如果是第二種,可能不能保證包含所有已經commit的消息,因此會造成數據丟失,但保證了可用性。目前KAFKA選用的是第二種方式,支持選擇不能保證一致的副本。你也可以通過參數unclean.leader.election.enable禁用它。

Broker宕機怎麼辦?

Controller在Zookeeper的/brokers/ids節點上註冊Watch。一旦有Broker宕機(本文用宕機代表任何讓Kafka認爲其Broker die的情景,包括但不限於機器斷電,網絡不可用,GC導致的Stop The World,進程crash等),其在Zookeeper對應的Znode會自動被刪除,Zookeeper會fire Controller註冊的Watch,Controller即可獲取最新的倖存的Broker列表。

Controller決定set_p,該集合包含了宕機的所有Broker上的所有Partition。
對set_p中的每一個Partition:

  • 從/brokers/topics/[topic]/partitions/[partition]/state讀取該Partition當前的ISR。
  • 決定該Partition的新Leader。如果當前ISR中有至少一個Replica還倖存,則選擇其中一個作爲新Leader,新的ISR則包含當前ISR中所有幸存的Replica。否則選擇該Partition中任意一個倖存的Replica作爲新的Leader以及ISR(該場景下可能會有潛在的數據丟失)。如果該Partition的所有Replica都宕機了,則將新的Leader設置爲-1。
  • 將新的Leader,ISR和新的leader_epoch及controller_epoch寫入/brokers/topics/[topic]/partitions/[partition]/state。
    [zk: localhost:2181(CONNECTED) 13] get /brokers/topics/bdstar/partitions/0/state
    {“controller_epoch”:1272,“leader”:0,“version”:1,“leader_epoch”:4,“isr”:[0,2]}

直接通過RPC向set_p相關的Broker發送LeaderAndISRRequest命令。Controller可以在一個RPC操作中發送多個命令從而提高效率。

Controller宕機怎麼辦?

每個Broker都會在/controller上註冊一個Watch。當前Controller宕機時(此處我kill了id=1的broker進程),對應的/controller會自動消失。所有“活”着的Broker都會去競選成爲新的Controller,會創建新的Controller Path。注意:只會有一個競選成功(這點由Zookeeper保證)。競選成功者即爲新的Leader,競選失敗者則重新在新的Controller Path上註冊Watch。因爲Zookeeper的Watch是一次性的,被fire一次之後即失效,所以需要重新註冊。

參考地址:

http://bigdata-star.com/archives/1510

http://developer.51cto.com/art/201904/595538.htm

http://www.jasongj.com/2015/04/24/KafkaColumn2/

https://blog.csdn.net/C_J33/article/details/82730041

http://www.thinkyixia.com/2017/10/25/kafka-2/#kafka%E6%8B%93%E6%89%91%E7%BB%93%E6%9E%84

http://lxw1234.com/archives/2015/09/504.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章