問題要從一次Kafka的當機開始說起。

筆者所在的是一家金融科技公司,但公司内部并沒有采用在金融支付領域更為流行的 RabbitMQ ,而是采用了設計之初就為日志處理而生的 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 的高可用。當某個 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。
另外,這裡補充一個面試考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?
問題出在了 <code>__consumer_offset</code> 上, <code>__consumer_offset</code> 是一個 Kafka 自動建立的 Topic,用來存儲消費者消費的 offset (偏移量)資訊,預設 Partition 數為50。而就是這個Topic,它的預設副本數為1。如果所有的 Partition 都存在于同一台機器上,那就是很明顯的單點故障了!當将存儲 <code>__consumer_offset</code> 的 Partition 的 Broker 給 Kill 後,會發現所有的消費者都停止消費了。
這個問題怎麼解決?
第一點 ,需要将 <code>__consumer_offset</code> 删除,注意這個Topic時Kafka内置的Topic,無法用指令删除,我是通過将 logs 删了來實作删除。
第二點 ,需要通過設定 offsets.topic.replication.factor 為3來将 <code>__consumer_offset</code> 的副本數改為3。通過将 <code>__consumer_offset</code> 也做副本備援後來解決某個節點當機後消費者的消費問題。
最後,關于為什麼 <code>__consumer_offset</code> 的 Partition 會出現隻存儲在一個 Broker 上而不是分布在各個 Broker 上感到困惑。
作者:JanusWoo
來源:https://juejin.im/post/6874957625998606344