直击Kafka的心脏——控制器
歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰(zhàn)指南》,同時歡迎關注筆者的微信公眾號:朱小廝的博客。
歡迎跳轉到本文的原文鏈接:https://honeypps.com/mq/kafka-controller-analysis/
在Kafka集群中會有一個或者多個broker,其中有一個broker會被選舉為控制器(Kafka Controller),它負責管理整個集群中所有分區(qū)和副本的狀態(tài)。當某個分區(qū)的leader副本出現故障時,由控制器負責為該分區(qū)選舉新的leader副本。當檢測到某個分區(qū)的ISR集合發(fā)生變化時,由控制器負責通知所有broker更新其元數據信息。當使用kafka-topics.sh腳本為某個topic增加分區(qū)數量時,同樣還是由控制器負責分區(qū)的重新分配。
Kafka中的控制器選舉的工作依賴于Zookeeper,成功競選為控制器的broker會在Zookeeper中創(chuàng)建/controller這個臨時(EPHEMERAL)節(jié)點,此臨時節(jié)點的內容參考如下:
{"version":1,"brokerid":0,"timestamp":"1529210278988"}其中version在目前版本中固定為1,brokerid表示稱為控制器的broker的id編號,timestamp表示競選稱為控制器時的時間戳。
在任意時刻,集群中有且僅有一個控制器。每個broker啟動的時候會去嘗試去讀取/controller節(jié)點的brokerid的值,如果讀取到brokerid的值不為-1,則表示已經有其它broker節(jié)點成功競選為控制器,所以當前broker就會放棄競選;如果Zookeeper中不存在/controller這個節(jié)點,或者這個節(jié)點中的數據異常,那么就會嘗試去創(chuàng)建/controller這個節(jié)點,當前broker去創(chuàng)建節(jié)點的時候,也有可能其他broker同時去嘗試創(chuàng)建這個節(jié)點,只有創(chuàng)建成功的那個broker才會成為控制器,而創(chuàng)建失敗的broker則表示競選失敗。每個broker都會在內存中保存當前控制器的brokerid值,這個值可以標識為activeControllerId。
Zookeeper中還有一個與控制器有關的/controller_epoch節(jié)點,這個節(jié)點是持久(PERSISTENT)節(jié)點,節(jié)點中存放的是一個整型的controller_epoch值。controller_epoch用于記錄控制器發(fā)生變更的次數,即記錄當前的控制器是第幾代控制器,我們也可以稱之為“控制器的紀元”。controller_epoch的初始值為1,即集群中第一個控制器的紀元為1,當控制器發(fā)生變更時,沒選出一個新的控制器就將該字段值加1。每個和控制器交互的請求都會攜帶上controller_epoch這個字段,如果請求的controller_epoch值小于內存中的controller_epoch值,則認為這個請求是向已經過期的控制器所發(fā)送的請求,那么這個請求會被認定為無效的請求。如果請求的controller_epoch值大于內存中的controller_epoch值,那么則說明已經有新的控制器當選了。由此可見,Kafka通過controller_epoch來保證控制器的唯一性,進而保證相關操作的一致性。
具備控制器身份的broker需要比其他普通的broker多一份職責,具體細節(jié)如下:
這個列表可能會讓讀者感到困惑,甚至完全不知所云。不要方~ 筆者這里只是用來突出控制器的職能很多,而這些功能的具體細節(jié)會在后面的文章中做具體的介紹。
控制器在選舉成功之后會讀取Zookeeper中各個節(jié)點的數據來初始化上下文信息(ControllerContext),并且也需要管理這些上下文信息,比如為某個topic增加了若干個分區(qū),控制器在負責創(chuàng)建這些分區(qū)的同時也要更新上下文信息,并且也需要將這些變更信息同步到其他普通的broker節(jié)點中。不管是監(jiān)聽器觸發(fā)的事件,還是定時任務觸發(fā)的事件,亦或者是其他事件(比如ControlledShutdown)都會讀取或者更新控制器中的上下文信息,那么這樣就會涉及到多線程間的同步,如果單純的使用鎖機制來實現,那么整體的性能也會大打折扣。針對這一現象,Kafka的控制器使用單線程基于事件隊列的模型,將每個事件都做一層封裝,然后按照事件發(fā)生的先后順序暫存到LinkedBlockingQueue中,然后使用一個專用的線程(ControllerEventThread)按照FIFO(First Input First Output, 先入先出)的原則順序處理各個事件,這樣可以不需要鎖機制就可以在多線程間維護線程安全。
在Kafka的早期版本中,并沒有采用Kafka Controller這樣一個概念來對分區(qū)和副本的狀態(tài)進行管理,而是依賴于Zookeeper,每個broker都會在Zookeeper上為分區(qū)和副本注冊大量的監(jiān)聽器(Watcher)。當分區(qū)或者副本狀態(tài)變化時,會喚醒很多不必要的監(jiān)聽器,這種嚴重依賴于Zookeeper的設計會有腦裂、羊群效應以及造成Zookeeper過載的隱患。在目前的新版本的設計中,只有Kafka Controller在Zookeeper上注冊相應的監(jiān)聽器,其他的broker極少需要再監(jiān)聽Zookeeper中的數據變化,這樣省去了很多不必要的麻煩。不過每個broker還是會對/controller節(jié)點添加監(jiān)聽器的,以此來監(jiān)聽此節(jié)點的數據變化(參考ZkClient中的IZkDataListener)。
當/controller節(jié)點的數據發(fā)生變化時,每個broker都會更新自身內存中保存的activeControllerId。如果broker在數據變更前是控制器,那么如果在數據變更后自身的brokerid值與新的activeControllerId值不一致的話,那么就需要“退位”,關閉相應的資源,比如關閉狀態(tài)機、注銷相應的監(jiān)聽器等。有可能控制器由于異常而下線,造成/controller這個臨時節(jié)點會被自動刪除;也有可能是其他原因將此節(jié)點刪除了。
當/controller節(jié)點被刪除時,每個broker都會進行選舉,如果broker在節(jié)點被刪除前是控制器的話,在選舉前還需要有一個“退位”的動作。如果有特殊需要,可以手動刪除/controller節(jié)點來觸發(fā)新一輪的選舉。當然關閉控制器所對應的broker以及手動向/controller節(jié)點寫入新的brokerid的所對應的數據同樣可以觸發(fā)新一輪的選舉。
歡迎跳轉到本文的原文鏈接:https://honeypps.com/mq/kafka-controller-analysis/
歡迎支持筆者新作:《深入理解Kafka:核心設計與實踐原理》和《RabbitMQ實戰(zhàn)指南》,同時歡迎關注筆者的微信公眾號:朱小廝的博客。
總結
以上是生活随笔為你收集整理的直击Kafka的心脏——控制器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kafka参数图鉴——unclean.l
- 下一篇: Kafka分区分配策略(1)——Rang