阿里P8面試題:Kafka如何做到發(fā)送端和接收端的順序一致性?
今天這篇文章,寫一個(gè)面試題的詳解。
最近半個(gè)月在弄部門明年預(yù)算,弄的頭大。終于弄的差不多了,這幾天趕緊抽空學(xué)習(xí)充電。
為了帶著問(wèn)題去學(xué)習(xí),我特意找一個(gè)大廠朋友要了一份他們的面試題,公司名不說(shuō)了,難度大概相當(dāng)于 P8。
面試題里有這么一道題:
Kafka 如何做到發(fā)送端和接收端的順序一致性?
我在網(wǎng)上找了找資料、答案,結(jié)果發(fā)現(xiàn)很多寫的不對(duì),或者寫的已經(jīng)過(guò)時(shí)了。
于是,我決定自己寫篇文章,詳解一下這道題。
Producer 端:
Kafka 的發(fā)送端發(fā)送消息,如果是默認(rèn)參數(shù)什么都不設(shè)置,則消息如果在網(wǎng)絡(luò)沒(méi)有抖動(dòng)的時(shí)候,可以一批批的按消息發(fā)送的順序被發(fā)送到 Kafka 服務(wù)器端。但是,一旦網(wǎng)絡(luò)波動(dòng)了,則消息就可能出現(xiàn)失序。
所以,要嚴(yán)格保證 Kafka 發(fā)消息有序,首先要考慮同步發(fā)送消息。
同步發(fā)送消息有兩種方式:
第一種方式:設(shè)置消息響應(yīng)參數(shù) acks > 0,最好是 -1。
然后,設(shè)置
max.in.flight.requests.per.connection = 1
這樣設(shè)置完后,在 Kafka 的發(fā)送端,將會(huì)一條消息發(fā)出后,響應(yīng)必須滿足 acks 設(shè)置的參數(shù)后,才會(huì)發(fā)送下一條消息。所以,雖然在使用時(shí),還是異步發(fā)送的方式,其實(shí)底層已經(jīng)是一條接一條的發(fā)送了。
第二種方式:當(dāng)調(diào)用 KafkaProducer 的 send 方法后,調(diào)用 send 方法返回的 Future 對(duì)象的 get 方式阻塞等待結(jié)果。等結(jié)果返回后,再繼續(xù)調(diào)用 KafkaProducer 的 send 方法發(fā)送下一條消息。
同步發(fā)送消息之外,還要考慮消息重發(fā)問(wèn)題。
Kafka 發(fā)送端可以在發(fā)送出現(xiàn)問(wèn)題時(shí),判斷問(wèn)題是否可以自動(dòng)恢復(fù),如果是可以自動(dòng)恢復(fù)的問(wèn)題,可以通過(guò)設(shè)置 retries > 0,讓 Kafka 自動(dòng)重試。
根據(jù) Kafka 版本的不同,Kafka 1.0 之后的版本,發(fā)送端引入了冪等特性。引入冪等特性,我們可以這么設(shè)置
enable.idempotence = true
冪等特性這個(gè)特性可以給消息添加序列號(hào),每次發(fā)送,會(huì)把序列號(hào)遞增 1。
開啟了 Kafka 發(fā)送端的冪等特性后,我們就可以設(shè)置
max.in.flight.requests.per.connection = 5
這樣,當(dāng) Kafka 發(fā)消息的時(shí)候,由于消息有了序列號(hào),當(dāng)發(fā)送消息出現(xiàn)錯(cuò)誤的時(shí)候,在 Kafka 底層會(huì)通過(guò)獲取服務(wù)器端的最近幾條日志的序列號(hào)和發(fā)送端需要重新發(fā)送的消息序列號(hào)做對(duì)比,如果是連續(xù)的,那么就可以繼續(xù)發(fā)送消息,保證消息順序。
Broker 端:
Kafka 的 Topic 只是一個(gè)邏輯概念。而組成 Topic 的分區(qū)才是真正存消息的地方。
Kafka 只保證同個(gè)分區(qū)內(nèi)的消息是有序的。所以,如果要保證業(yè)務(wù)全局嚴(yán)格有序,就要設(shè)置 Topic 為單分區(qū)的形式。
不過(guò),往往我們的業(yè)務(wù)是不需要考慮全局有序的,我們只需要保證業(yè)務(wù)中不同類別的消息有序即可。對(duì)這些業(yè)務(wù)中不同類別的消息,可以設(shè)置成不同的 Key,然后根據(jù) Key 取模。這樣,由于同類別消息有同樣的 Key,就會(huì)被分配到同樣的分區(qū)中,保證有序。
但是,這里有個(gè)問(wèn)題,就是當(dāng)我們對(duì)分區(qū)的數(shù)量進(jìn)行改變的時(shí)候,會(huì)把以前可能分到同樣的分區(qū)的消息,分到別的分區(qū)上。這就不能保證消息順序了。
面對(duì)這種情況,就需要在動(dòng)態(tài)變更分區(qū)的時(shí)候,考慮對(duì)業(yè)務(wù)的影響。有可能需要根據(jù)業(yè)務(wù)和當(dāng)前分區(qū)需求,重新劃分消息類別。
另外,如果一個(gè) Topic 存在多分區(qū)的情況,并且 min.insync.replicas 指定的副本個(gè)數(shù)掛掉了,那么,就會(huì)出現(xiàn)這種情況:發(fā)送消息寫入不了對(duì)應(yīng)分區(qū),但是消費(fèi)依然可以消費(fèi)消息。
此時(shí),往往我們會(huì)保證可用性,會(huì)考慮切換消息的分區(qū),一旦這樣做,消息順序就可能出現(xiàn)不一致的情況。
所以,一定要保證 min.insync.replicas 參數(shù)配置的合適,去最大可能保證消息寫入的順序性。
Consumer 端:
在消費(fèi)者端,根據(jù) Kafka 的模型,一個(gè) Topic 下的每個(gè)分區(qū)只能從屬于監(jiān)聽這個(gè) Topic 的消費(fèi)者組中的某一個(gè)消費(fèi)者。
假設(shè) Topic 的分區(qū)數(shù)量為 P,而消費(fèi)者組中的消費(fèi)者數(shù)為 C。那么,如果 P < C , 就會(huì)出現(xiàn)消費(fèi)者空閑的情況;如果 P > C,則會(huì)出現(xiàn)一個(gè)消費(fèi)者被分配多個(gè)分區(qū)的情況,如下圖。

所以,當(dāng)我們消費(fèi)者端使用 poll 方法的時(shí)候,一定要注意:poll 方法獲取到的記錄,很可能是多個(gè)分區(qū)甚至多個(gè) Topic 的。
還需要通過(guò) ConsumerRecords 的 records(TopicPartition partition) 進(jìn)行進(jìn)一步的排序和篩選,才能真正的保證發(fā)送和消費(fèi)的順序一致性使用。
另外一個(gè)要注意的地方就是消費(fèi)者的 Rebalance。Rebalance 就是讓一個(gè)消費(fèi)者組下所有的消費(fèi)者實(shí)例,就如何消費(fèi)訂閱主題的所有分區(qū)達(dá)成共識(shí)的過(guò)程。
這個(gè) Rebalance 機(jī)制是 Kafka 最臭名照顧的地方:
它每次 Rebalance,都會(huì)讓全部消費(fèi)者組的消費(fèi)暫停。
再就是 Rebalance 的 bug 非常多,比如就是 Rebalance 后,要么某個(gè)消費(fèi)者突然崩了,要么是消費(fèi)者組中某些消費(fèi)者停了。
由于 Rebalance 相當(dāng)于讓消費(fèi)者組重新分配分區(qū),這就可能造成消費(fèi)者在 Rebalance 前、后所對(duì)應(yīng)的分區(qū)不一致。分區(qū)不一致,那自然消費(fèi)順序就不可能一致了。
所以,我們都會(huì)盡量不讓 Rebalance 發(fā)生。有三種情況會(huì)觸發(fā) Kafka 消費(fèi)者的 Rebalance 發(fā)生:
消費(fèi)者組成員發(fā)生變化:這個(gè)往往是指,我們認(rèn)為增減了組內(nèi)的消費(fèi)者個(gè)數(shù),又或者是某些消費(fèi)者崩潰了,導(dǎo)致被踢出組。
訂閱主題數(shù)發(fā)生變化:Kafka 的消費(fèi)者組是能用正則去模糊匹配 Topic 的。這就造成一個(gè)問(wèn)題,當(dāng)我們?cè)?Kafka 中添加主題后,可能會(huì)造成消費(fèi)者組監(jiān)聽的 Topic 數(shù)發(fā)生變化。
訂閱主題的分區(qū)數(shù)發(fā)生變化:有些時(shí)候,可能我們想動(dòng)態(tài)的線上變更主題的分區(qū)數(shù)。
所以,當(dāng)這三種情況觸發(fā) Rebalance 后,就會(huì)出現(xiàn)問(wèn)題,消費(fèi)順序不一致只是其中很輕微的一種負(fù)面影響。
寫到這里,基本算是徹底把這道面試題回答完整。
其中 Producer 端的冪等性質(zhì),Consumer 端的 Rebalance 情況,是最容易在回答 Kafka 順序一致性類似面試題中漏掉的。而很多網(wǎng)上的面試題答案都已經(jīng)相對(duì)過(guò)時(shí)了,沒(méi)有談過(guò)這兩個(gè)特性相關(guān)的問(wèn)題。
所以,大家做面試準(zhǔn)備的時(shí)候,一定要好好的復(fù)習(xí)相關(guān)中間件的知識(shí)和特性,而不是去死記硬背答案。希望大家學(xué)習(xí)的時(shí)候多加注意。
最后求點(diǎn)贊、在看、轉(zhuǎn)發(fā),建議大家收藏這篇文章,面試前再看看。
你好,我是四猿外。
一家上市公司的技術(shù)總監(jiān),管理的技術(shù)團(tuán)隊(duì)一百余人。想了解我如何管理團(tuán)隊(duì)——我,管理100多人團(tuán)隊(duì)的二三事
我從一名非計(jì)算機(jī)專業(yè)的畢業(yè)生,轉(zhuǎn)行到程序員,一路打拼,一路成長(zhǎng)。
我會(huì)通過(guò)公眾號(hào),
把自己的成長(zhǎng)故事寫成文章,
把枯燥的技術(shù)文章寫成故事。
我建了一個(gè)讀者交流群,里面大部分是程序員,一起聊技術(shù)、工作、八卦。歡迎加我微信,拉你入群。
推薦閱讀
