因?yàn)橐淮?Kafka 宕機(jī),終于搞透了 Kafka 高可用原理!
- Kafka 宕機(jī)引發(fā)的高可用思考 -
問(wèn)題要從一次Kafka的宕機(jī)開(kāi)始說(shuō)起。
但最近系統(tǒng)測(cè)試人員常反饋偶有Kafka消費(fèi)者收不到消息的情況,登陸管理界面發(fā)現(xiàn)三個(gè)節(jié)點(diǎn)中有一個(gè)節(jié)點(diǎn)宕機(jī)掛掉了。但是按照高可用的理念,三個(gè)節(jié)點(diǎn)還有兩個(gè)節(jié)點(diǎn)可用怎么就引起了整個(gè)集群的消費(fèi)者都接收不到消息呢?
要解決這個(gè)問(wèn)題,就要從Kafka的高可用實(shí)現(xiàn)開(kāi)始講起。
- Kafka 的多副本冗余設(shè)計(jì) -
不管是傳統(tǒng)的基于關(guān)系型數(shù)據(jù)庫(kù)設(shè)計(jì)的系統(tǒng),還是分布式的如 zookeeper 、 redis 、 Kafka 、 HDFS等等,實(shí)現(xiàn)高可用的辦法通常是采用冗余設(shè)計(jì),通過(guò)冗余來(lái)解決節(jié)點(diǎn)宕機(jī)不可用問(wèn)題。
首先簡(jiǎn)單了解Kafka的幾個(gè)概念:
物理模型
-
Broker (節(jié)點(diǎn)):Kafka服務(wù)節(jié)點(diǎn),簡(jiǎn)單來(lái)說(shuō)一個(gè) Broker 就是一臺(tái)Kafka服務(wù)器,一個(gè)物理節(jié)點(diǎn)。
-
Topic (主題):在Kafka中消息以主題為單位進(jìn)行歸類,每個(gè)主題都有一個(gè) Topic Name ,生產(chǎn)者根據(jù)Topic Name將消息發(fā)送到特定的Topic,消費(fèi)者則同樣根據(jù)Topic Name從對(duì)應(yīng)的Topic進(jìn)行消費(fèi)。
-
Partition (分區(qū)):Topic (主題)是消息歸類的一個(gè)單位,但每一個(gè)主題還能再細(xì)分為一個(gè)或多個(gè) Partition (分區(qū)),一個(gè)分區(qū)只能屬于一個(gè)主題。主題和分區(qū)都是邏輯上的概念,舉個(gè)例子,消息1和消息2都發(fā)送到主題1,它們可能進(jìn)入同一個(gè)分區(qū)也可能進(jìn)入不同的分區(qū)(所以同一個(gè)主題下的不同分區(qū)包含的消息是不同的),之后便會(huì)發(fā)送到分區(qū)對(duì)應(yīng)的Broker節(jié)點(diǎn)上。
- Offset (偏移量):分區(qū)可以看作是一個(gè)只進(jìn)不出的隊(duì)列(Kafka只保證一個(gè)分區(qū)內(nèi)的消息是有序的),消息會(huì)往這個(gè)隊(duì)列的尾部追加,每個(gè)消息進(jìn)入分區(qū)后都會(huì)有一個(gè)偏移量,標(biāo)識(shí)該消息在該分區(qū)中的位置,消費(fèi)者要消費(fèi)該消息就是通過(guò)偏移量來(lái)識(shí)別。
在Kafka 0.8版本以前,是沒(méi)有多副本冗余機(jī)制的,一旦一個(gè)節(jié)點(diǎn)掛掉,那么這個(gè)節(jié)點(diǎn)上的所有 Partition的數(shù)據(jù)就無(wú)法再被消費(fèi)。這就等于發(fā)送到Topic的有一部分?jǐn)?shù)據(jù)丟失了。
在0.8版本后引入副本記者則很好地解決宕機(jī)后數(shù)據(jù)丟失的問(wèn)題。副本是以 Topic 中每個(gè) Partition的數(shù)據(jù)為單位,每個(gè)Partition的數(shù)據(jù)會(huì)同步到其他物理節(jié)點(diǎn)上,形成多個(gè)副本。
每個(gè) Partition 的副本都包括一個(gè) Leader 副本和多個(gè) Follower副本,Leader由所有的副本共同選舉得出,其他副本則都為Follower副本。在生產(chǎn)者寫(xiě)或者消費(fèi)者讀的時(shí)候,都只會(huì)與Leader打交道,在寫(xiě)入數(shù)據(jù)后Follower就會(huì)來(lái)拉取數(shù)據(jù)進(jìn)行數(shù)據(jù)同步。
就這么簡(jiǎn)單?是的,基于上面這張多副本架構(gòu)圖就實(shí)現(xiàn)了Kafka的高可用。當(dāng)某個(gè) Broker 掛掉了,甭?lián)?,這個(gè) Broker 上的 Partition 在其他 Broker 節(jié)點(diǎn)上還有副本。你說(shuō)如果掛掉的是 Leader 怎么辦?那就在 Follower中在選舉出一個(gè) Leader 即可,生產(chǎn)者和消費(fèi)者又可以和新的 Leader 愉快地玩耍了,這就是高可用。
你可能還有疑問(wèn),那要多少個(gè)副本才算夠用?Follower和Leader之間沒(méi)有完全同步怎么辦?一個(gè)節(jié)點(diǎn)宕機(jī)后Leader的選舉規(guī)則是什么?
直接拋結(jié)論:
多少個(gè)副本才算夠用? 副本肯定越多越能保證Kafka的高可用,但越多的副本意味著網(wǎng)絡(luò)、磁盤(pán)資源的消耗更多,性能會(huì)有所下降,通常來(lái)說(shuō)副本數(shù)為3即可保證高可用,極端情況下將 replication-factor 參數(shù)調(diào)大即可。
Follower和Lead之間沒(méi)有完全同步怎么辦? Follower和Leader之間并不是完全同步,但也不是完全異步,而是采用一種 ISR機(jī)制( In-Sync Replica)。每個(gè)Leader會(huì)動(dòng)態(tài)維護(hù)一個(gè)ISR列表,該列表里存儲(chǔ)的是和Leader基本同步的Follower。如果有Follower由于網(wǎng)絡(luò)、GC等原因而沒(méi)有向Leader發(fā)起拉取數(shù)據(jù)請(qǐng)求,此時(shí)Follower相對(duì)于Leader是不同步的,則會(huì)被踢出ISR列表。所以說(shuō),ISR列表中的Follower都是跟得上Leader的副本。
一個(gè)節(jié)點(diǎn)宕機(jī)后Leader的選舉規(guī)則是什么? 分布式相關(guān)的選舉規(guī)則有很多,像Zookeeper的 Zab 、 Raft 、 Viewstamped Replication 、微軟的 PacificA 等。而Kafka的Leader選舉思路很簡(jiǎn)單,基于我們上述提到的 ISR列表,當(dāng)宕機(jī)后會(huì)從所有副本中順序查找,如果查找到的副本在ISR列表中,則當(dāng)選為L(zhǎng)eader。另外還要保證前任Leader已經(jīng)是退位狀態(tài)了,否則會(huì)出現(xiàn)腦裂情況(有兩個(gè)Leader)。怎么保證?Kafka通過(guò)設(shè)置了一個(gè)controller來(lái)保證只有一個(gè)Leader。
- Ack 參數(shù)決定了可靠程度 -
另外,這里補(bǔ)充一個(gè)面試考Kafka高可用必備知識(shí)點(diǎn):request.required.asks 參數(shù)。
Asks這個(gè)參數(shù)是生產(chǎn)者客戶端的重要配置,發(fā)送消息的時(shí)候就可設(shè)置這個(gè)參數(shù)。該參數(shù)有三個(gè)值可配置:0、1、All 。
第一種是設(shè)為0 意思是生產(chǎn)者把消息發(fā)送出去之后,之后這消息是死是活咱就不管了,有那么點(diǎn)發(fā)后即忘的意思,說(shuō)出去的話就不負(fù)責(zé)了。不負(fù)責(zé)自然這消息就有可能丟失,那就把可用性也丟失了。
第二種是設(shè)為1 意思是生產(chǎn)者把消息發(fā)送出去之后,這消息只要順利傳達(dá)給了Leader,其他Follower有沒(méi)有同步就無(wú)所謂了。存在一種情況,Leader剛收到了消息,F(xiàn)ollower還沒(méi)來(lái)得及同步Broker就宕機(jī)了,但生產(chǎn)者已經(jīng)認(rèn)為消息發(fā)送成功了,那么此時(shí)消息就丟失了。
設(shè)為1是Kafka的默認(rèn)配置 可見(jiàn)Kafka的默認(rèn)配置也不是那么高可用,而是對(duì)高可用和高吞吐量做了權(quán)衡折中。
第三種是設(shè)為All(或者-1) 意思是生產(chǎn)者把消息發(fā)送出去之后,不僅Leader要接收到,ISR列表中的Follower也要同步到,生產(chǎn)者才會(huì)任務(wù)消息發(fā)送成功。
進(jìn)一步思考, Asks=All 就不會(huì)出現(xiàn)丟失消息的情況嗎?答案是否。當(dāng)ISR列表只剩Leader的情況下, Asks=All 相當(dāng)于 Asks=1 ,這種情況下如果節(jié)點(diǎn)宕機(jī)了,還能保證數(shù)據(jù)不丟失嗎?因此只有在 Asks=All并且有ISR中有兩個(gè)副本的情況下才能保證數(shù)據(jù)不丟失。
- 解決問(wèn)題 -
繞了一大圈,了解了Kafka的高可用機(jī)制,終于回到我們一開(kāi)始的問(wèn)題本身, Kafka 的一個(gè)節(jié)點(diǎn)宕機(jī)后為什么不可用?
我在開(kāi)發(fā)測(cè)試環(huán)境配置的 Broker 節(jié)點(diǎn)數(shù)是3, Topic 是副本數(shù)為3, Partition 數(shù)為6, Asks參數(shù)為1。
當(dāng)三個(gè)節(jié)點(diǎn)中某個(gè)節(jié)點(diǎn)宕機(jī)后,集群首先會(huì)怎么做?沒(méi)錯(cuò),正如我們上面所說(shuō)的,集群發(fā)現(xiàn)有Partition的Leader失效了,這個(gè)時(shí)候就要從ISR列表中重新選舉Leader。如果ISR列表為空是不是就不可用了?并不會(huì),而是從Partition存活的副本中選擇一個(gè)作為L(zhǎng)eader,不過(guò)這就有潛在的數(shù)據(jù)丟失的隱患了。
所以,只要將Topic副本個(gè)數(shù)設(shè)置為和Broker個(gè)數(shù)一樣,Kafka的多副本冗余設(shè)計(jì)是可以保證高可用的,不會(huì)出現(xiàn)一宕機(jī)就不可用的情況(不過(guò)需要注意的是Kafka有一個(gè)保護(hù)策略,當(dāng)一半以上的節(jié)點(diǎn)不可用時(shí)Kafka就會(huì)停止)。那仔細(xì)一想,Kafka上是不是有副本個(gè)數(shù)為1的Topic?
問(wèn)題出在了 __consumer_offset 上, __consumer_offset 是一個(gè)Kafka自動(dòng)創(chuàng)建的 Topic,用來(lái)存儲(chǔ)消費(fèi)者消費(fèi)的 offset (偏移量)信息,默認(rèn) Partition數(shù)為50。而就是這個(gè)Topic,它的默認(rèn)副本數(shù)為1。如果所有的 Partition 都存在于同一臺(tái)機(jī)器上,那就是很明顯的單點(diǎn)故障了!當(dāng)將存儲(chǔ) __consumer_offset 的Partition的Broker給Kill后,會(huì)發(fā)現(xiàn)所有的消費(fèi)者都停止消費(fèi)了。
這個(gè)問(wèn)題怎么解決? 第一點(diǎn) ,需要將 __consumer_offset 刪除,注意這個(gè)Topic時(shí)Kafka內(nèi)置的Topic,無(wú)法用命令刪除,我是通過(guò)將 logs 刪了來(lái)實(shí)現(xiàn)刪除。
第二點(diǎn) ,需要通過(guò)設(shè)置 offsets.topic.replication.factor 為3來(lái)將 __consumer_offset 的副本數(shù)改為3。通過(guò)將 __consumer_offset 也做副本冗余后來(lái)解決某個(gè)節(jié)點(diǎn)宕機(jī)后消費(fèi)者的消費(fèi)問(wèn)題。
最后,關(guān)于為什么 __consumer_offset的Partition會(huì)出現(xiàn)只存儲(chǔ)在一個(gè)Broker上而不是分布在各個(gè)Broker上感到困惑。
作者:JanusWoo
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!