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