Kafka宕機引發的高可用問題
問題要從一次Kafka的宕機開始說起。筆者所在的是一家金融科技公司,但公司內部並沒有採用在金融支付領域更為流行的RabbitMQ,而是採用了設計之初就為日誌處理而生的Kafka,所以我一直很好奇Kafka的高可用實現和保障。從Kafka部署後,系統內部使用的Kafka一直執行穩定,沒有出現不可用的情況。但最近系統測試人員常反饋偶有Kafka消費者收不到訊息的情況,登陸管理介面發現三個節點中有一個節點宕機掛掉了。但是按照高可用的理念,三個節點還有兩個節點可用怎麼就引起了整個叢集的消費者都接收不到訊息呢?要解決這個問題,就要從Kafka的高可用實現開始講起。
Kafka的多副本冗餘設計不管是傳統的基於關係型資料庫設計的系統,還是分散式的如zookeeper、redis、Kafka、HDFS等等,實現高可用的辦法通常是採用冗餘設計,透過冗餘來解決節點宕機不可用問題。首先簡單瞭解Kafka的幾個概念:
物理模型邏輯模型Broker(節點):Kafka服務節點,簡單來說一個Broker就是一臺Kafka伺服器,一個物理節點。Topic(主題):在Kafka中訊息以主題為單位進行歸類,每個主題都有一個Topic Name,生產者根據Topic Name將訊息傳送到特定的Topic,消費者則同樣根據Topic Name從對應的Topic進行消費。Partition(分割槽):Topic(主題)是訊息歸類的一個單位,但每一個主題還能再細分為一個或多個Partition(分割槽),一個分割槽只能屬於一個主題。主題和分割槽都是邏輯上的概念,舉個例子,訊息1和訊息2都發送到主題1,它們可能進入同一個分割槽也可能進入不同的分割槽(所以同一個主題下的不同分割槽包含的訊息是不同的),之後便會發送到分割槽對應的Broker節點上。Offset(偏移量):分割槽可以看作是一個只進不出的佇列(Kafka只保證一個分割槽內的訊息是有序的),訊息會往這個佇列的尾部追加,每個訊息進入分割槽後都會有一個偏移量,標識該訊息在該分割槽中的位置,消費者要消費該訊息就是透過偏移量來識別。其實,根據上述的幾個概念,是不是也多少猜到了Kafka的多副本冗餘設計實現了?別急,咱繼續往下看。
在Kafka 0.8版本以前,是沒有多副本冗餘機制的,一旦一個節點掛掉,那麼這個節點上的所有Partition的資料就無法再被消費。這就等於傳送到Topic的有一部分資料丟失了。
每個Partition的副本都包括一個Leader副本和多個Follower副本,Leader由所有的副本共同選舉得出,其他副本則都為Follower副本。在生產者寫或者消費者讀的時候,都只會與Leader打交道,在寫入資料後Follower就會來拉取資料進行資料同步。
就這麼簡單?是的,基於上面這張多副本架構圖就實現了Kafka的高可用。當某個Broker掛掉了,甭擔心,這個Broker上的Partition在其他Broker節點上還有副本。你說如果掛掉的是Leader怎麼辦?那就在Follower中在選舉出一個Leader即可,生產者和消費者又可以和新的Leader愉快地玩耍了,這就是高可用。
你可能還有疑問,那要多少個副本才算夠用?Follower和Leader之間沒有完全同步怎麼辦?一個節點宕機後Leader的選舉規則是什麼?
直接拋結論:
多少個副本才算夠用?副本肯定越多越能保證Kafka的高可用,但越多的副本意味著網路、磁碟資源的消耗更多,效能會有所下降,通常來說副本數為3即可保證高可用,極端情況下將replication-factor引數調大即可。
Follower和Lead之間沒有完全同步怎麼辦?Follower和Leader之間並不是完全同步,但也不是完全非同步,而是採用一種ISR機制(In-Sync Replica)。每個Leader會動態維護一個ISR列表,該列表裡儲存的是和Leader基本同步的Follower。如果有Follower由於網路、GC等原因而沒有向Leader發起拉取資料請求,此時Follower相對於Leader是不同步的,則會被踢出ISR列表。所以說,ISR列表中的Follower都是跟得上Leader的副本。
一個節點宕機後Leader的選舉規則是什麼?分散式相關的選舉規則有很多,像ZooKeeper的Zab、Raft、Viewstamped Replication、微軟的PacificA等。而Kafka的Leader選舉思路很簡單,基於我們上述提到的ISR列表,當宕機後會從所有副本中順序查詢,如果查詢到的副本在ISR列表中,則當選為Leader。另外還要保證前任Leader已經是退位狀態了,否則會出現腦裂情況(有兩個Leader)。怎麼保證?Kafka透過設定了一個controller來保證只有一個Leader。
Ack引數決定了可靠程度另外,這裡補充一個面試考Kafka高可用必備知識點:request.required.asks引數。
Asks這個引數是生產者客戶端的重要配置,傳送訊息的時候就可設定這個引數。該引數有三個值可配置:0、1、All。
第一種是設為0,意思是生產者把訊息傳送出去之後,之後這訊息是死是活咱就不管了,有那麼點發後即忘的意思,說出去的話就不負責了。不負責自然這訊息就有可能丟失,那就把可用性也丟失了。
第二種是設為1,意思是生產者把訊息傳送出去之後,這訊息只要順利傳達給了Leader,其他Follower有沒有同步就無所謂了。存在一種情況,Leader剛收到了訊息,Follower還沒來得及同步Broker就宕機了,但生產者已經認為訊息傳送成功了,那麼此時訊息就丟失了。注意,設為1是Kafka的預設配置!可見Kafka的預設配置也不是那麼高可用,而是對高可用和高吞吐量做了權衡折中。
第三種是設為All(或者-1),意思是生產者把訊息傳送出去之後,不僅Leader要接收到,ISR列表中的Follower也要同步到,生產者才會任務訊息傳送成功。
進一步思考,Asks=All就不會出現丟失訊息的情況嗎?答案是否。當ISR列表只剩Leader的情況下,Asks=All相當於Asks=1,這種情況下如果節點宕機了,還能保證資料不丟失嗎?因此只有在Asks=All並且有ISR中有兩個副本的情況下才能保證資料不丟失。
解決問題繞了一大圈,瞭解了Kafka的高可用機制,終於回到我們一開始的問題本身,Kafka的一個節點宕機後為什麼不可用?
我在開發測試環境配置的Broker節點數是3,Topic是副本數為3,Partition數為6,Asks引數為1。
當三個節點中某個節點宕機後,叢集首先會怎麼做?沒錯,正如我們上面所說的,叢集發現有Partition的Leader失效了,這個時候就要從ISR列表中重新選舉Leader。如果ISR列表為空是不是就不可用了?並不會,而是從Partition存活的副本中選擇一個作為Leader,不過這就有潛在的資料丟失的隱患了。
所以,只要將Topic副本個數設定為和Broker個數一樣,Kafka的多副本冗餘設計是可以保證高可用的,不會出現一宕機就不可用的情況(不過需要注意的是Kafka有一個保護策略,當一半以上的節點不可用時Kafka就會停止)。那仔細一想,Kafka上是不是有副本個數為1的Topic?
問題出在了__consumer_offset上,__consumer_offset是一個Kafka自動建立的Topic,用來儲存消費者消費的offset(偏移量)資訊,預設Partition數為50。而就是這個Topic,它的預設副本數為1。如果所有的Partition都存在於同一臺機器上,那就是很明顯的單點故障了!當將儲存__consumer_offset的Partition的Broker給Kill後,會發現所有的消費者都停止消費了。
這個問題怎麼解決?
第二點,需要透過設定offsets.topic.replication.factor為3來將__consumer_offset的副本數改為3。
透過將__consumer_offset也做副本冗餘後來解決某個節點宕機後消費者的消費問題。
最後,關於為什麼__consumer_offset的Partition會出現只儲存在一個Broker上而不是分佈在各個Broker上感到困惑,如果有朋友瞭解的煩請指教~