消息中間件 Kafka

消息中間件 Kafka

假設一種case,一個任務耗時高達數十秒,使用同步方式調用,一言不合便是超時,這可如何是好呢?

首相想到的,當然是異步化。耗時巨大的任務,使用同步調用方式,比如HTTP或者RPC,超時是很難避免的了。一個更好的做法便是將任務放入一個隊列,同時給這個任務賦予一個唯一標識符,根據這個唯一標識符去獲取任務的結果。隊列有許多種,比如著名的Kafka,Nsq,RabbitMq等等。不同的隊列有著不一樣的適用場景。

筆者今天來談談對Kafka這一消息中間件的理解。

Kafka可以說是當前最為完善,使用最廣泛的消息中間件。Kafka底層使用Scala語言編寫,運行時需要JVM環境支撐。Kafka通常需要與Zookeeper一同使用,Kafka集群信息存儲於Zookeeper中,每當有新的實例加入或者舊的實例退出時,都會在Zookeeper中進行實例信息的更改。Kafka 0.9之前的版本還會將offset存儲於Zookeeper中,0.9版本及之後,offset不再存儲於Zookeeper中,而改成存儲在broker實例中。關於Kafka的offset將會在下文中闡述,這也是Kafka最為重要的一個概念。

說說Kafka最基本的幾個概念。

Producer(生產者)

消息隊列的生產者端,也就是發消息的。生產者發送消息的時候,會指定發送到哪個topic。

Consumer(消費者)

消息隊列的消費者端。消費者會訂閱某個topic,它會接收到來自這個topic的消息。

Consumer Group(消費者組)

字面意思,消費者的組。對於同一個消費者組,組內會有多個消費者進程,一個消費者對應一個消費者進程。對於歸屬於同一組的消費者,此消費者組訂閱了一個topic,那麼,來自這個topic的消息只會被消費者組中的一個消費者進程消費,如圖Figure1所示。

消息中間件 Kafka

Figure1

對於兩個不同的消費者組,這兩個消費者組訂閱了同一個topic,那麼,來自這個topic的消息對於這兩個消費者組是廣播的,如圖Figure1所示。

Topic(主題)

上文中已提到。不同的topic之間是相互獨立開的,不同的topic裡面存放著不同的消息。通常一個Kafka隊列會有多個不同的業務方接入,不同的業務方使用不同的topic相互之間完全隔離。

Partition(分區)

對於一個topic,裡面有一個或者多個分區。生產者發送的消息進入到topic裡面之後,會均勻的分佈在不同的分區之中,如圖Figure2所示。

消息中間件 Kafka

Figure2

對於同一個分區內部,消息是保持有序的,但是在有多個分區的情況下,topic整體上消息無序。因此,如果有什麼業務場景要求消息保持絕對有序,一定要將分區數量設置為1。對於同一個消費者組內部,一個消費者進程可以消費一個或者多個分區的消息。如果消費者進程數量小於分區數量,那麼一個進程消費多個分區;如果消費者進程數量大於分區數量,那麼就會有進程被閒置,因為一個分區只能被一個消費者消費。因此,通常設置同一個消費者組內,消費者數量等於分區數量,此時能夠達到最高吞吐量。

Broker(實例,也就是機器)

Kafka通常由集群構成,集群中的一臺機器,也就是一臺實例,就是一個broker。

Offset(偏移量,用於記錄當前消費位置)

筆者認為,這是kafka中最為關鍵的一個概念。Kafka隊列的原理是,生產者寫入的消息會保存到本地磁盤文件,也就是說,生產者發送的所有的消息都會被保存下來。消費者消費消息時,消息不是真的被消費了,而是,消費一條消息之後,offset向後偏移一位,指向下一條消息的index,如圖Figure3所示。

消息中間件 Kafka

Figure3

在Figure3中,生產者從左側寫入消息,消費者的offset從右側開始消費偏移。圖中所示的是一個partition內部的情況,對於一個完整的topic,每個partition都會為每一個消費者組維持著一個offset值。什麼意思呢?對於一個消費者組,它訂閱了一個topic,也就是說,它正在消費這個topic中的所有partition,每個partition中都為這個消費者組,維護著一份offset,來表明該消費者組在這個partition中消費到了什麼位置。當這個topic有多個消費者組訂閱時,partition就會為每一個消費者組維護一份offset,因為不同的消費者組消費到的位置不一樣。Offset不是永遠只會往後移動的,他是可以自定義調整位置的。只要生產者

成功寫入了消息,消息就會被Kafka保存下來。如果消費者因為一些原因沒能消費成功,那麼可以通過修改偏移量offset來重新進行一次消費。在一些精確度要求很高的場景,這種特性十分適用,比如計費,點鈔票的時候因為一些原因點漏了可就不好了,Kafka存儲了所有消息,就算出現了異常,通過修改offset重來一次就好了。

那麼,有個問題,對於同一個消費者組內部,每個消費者進程,是怎樣被分配給topic裡面的partition的呢?

分配策略有很多,筆者在這裡說說Kafka本身自帶的默認的兩種策略,他們是RangeRoundRobin

Range策略

該策略十分簡單。假設現在有1~10合計10個分區,有1~3合計三個消費者進程。此時我們用 10 除以 3,得到3餘1。對於三個消費者進程,我們先分別給3個消費者進程分配3個分區。此時還有一個分區,我們將其分配給第一個消費者。於是乎,分區分配情況就是【(1, 2, 3, 4), (5, 6, 7), (8, 9, 10)】。第一個消費者進程分陪了1~4號分區,第二個消費者進程分配了5~7號分區,第三個消費者進程分配了8~10號分區。

再舉一個例子,假設現在有1~11合計11個分區,有1~3合計三個消費者進程。那麼11除以3等於3餘2。先平均分配,此時剩餘2個分區,我們再將這兩個分區分配給前面的兩個消費者進程,也就是【(1,2,3,4),(5,6,7,8),(9,10,11)】。

RoundRobin策略

該策略會無視Kafka的topic,它會將topic+partition視為一個整體,隨後計算其hashcode,根據計算出來的hashcode值,對所有的topic-partition進行排序。隨後,按照RoundRobin原則,將這些topic-partition均分給所有的消費者進程,如圖Figure4所示。

消息中間件 Kafka

Figure4

在只訂閱一個topic的情況下,上述兩種策略其實沒有特別大的區別。然而在訂閱了多個topic的情況下,RoundRobin策略將會優於Range策略。Figure5所示為Range策略的一個bad case。

消息中間件 Kafka

Figure5

我們按照Range策略進行了分區分配之後,我們發現,consumer1消費了6個分區,consumer2消費了5個分區,consumer3只消費了三個分區。那麼,這種分配就嚴重不均衡了。但如果是使用了RoundRobin策略,情況就會好的多,因為它將topic-partition視為了一個整體,不會因為topic的緣故導致分配不均勻,如圖Figure6所示。

消息中間件 Kafka

Figure6

在RoundRobin策略下,consumer1和consumer2各消費了4個partition,consumer3消費了3個partition,分配情況優於Range策略。

那麼當有消費者進程退出或者啟動的時候,會發生什麼呢?這種情況下,原先的分配就被打亂了吧?

是的,確實被打亂了,此時就會發生重平衡(rebalance)。重平衡發生於消費者進程數量發生變化的時候,為了保持分區分配的合理性,需重新進行一次分區分配。重平衡進行的時候,消費者暫停消費,不過時間不會太長,有可能導致短暫的服務不可用。如果監測到一個消費者服務頻繁地發生重平衡,應當檢查服務是否存在存在進程退出的case。


分享到:


相關文章: