Kafka消費者組與重平衡
Kafka消費者組
傳統的消息隊列處理模型主要有以下兩種:
-
隊列模型
類似隊列的數據結構,生產者生產消息入隊,消費者消費消息出隊,並刪除隊列中的數據,不能重復消費,這種模型只能由一個消費者消費,無法讓多個消費者同時消費同一條消息。
-
發布-訂閱模型
發布-訂閱模型,多了個主題的概念,消費者需要訂閱某個主題,生產者發送消息到主題中,訂閱了該主題的所有消費者都可以接收到該消息,可以滿足多個消費者同時消費同一條消息。
Kafka的消費者組設計,使得Kafka可以同時實現這兩種模型,同時還能對消費者組進行擴容,讓消費變得易伸縮
每個消費者組中包含一個或多個消費者,這些消費者實例共享一個id,成為group id,默認創建的group id 在KAFKA_HOME/conf/consumer.properties
可以配置,默認為test-consumer-group
,消費者組中的所有成員一起訂閱某個主題下的分區
注意:一個分區只能由組內的一個消費者訂閱
消費者組內消費者數量
因一個分區只能由一個消費者訂閱,所以一個消費者組中的消費者數量不應大於分區數量,多余的消費者不會分到任何分區,一般分區的數量為組內消費者的倍數,最好分區數和消費者數保持一致。
# 查看分組列表
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --list
# 查看具體到某個組的消費者情況
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group my-group
重平衡機制
重平衡(Reblalance)機制決定了如何讓消費者組中的消費者來分配topic下的分區,重平衡的觸發條件有以下三個:
- 消費者組內成員發生變更,這個變更包括了增加和減少消費者。注意這里的減少有很大的可能是被動的,就是某個消費者崩潰退出了
- 主題的分區數發生變更,kafka目前只支持增加分區,當增加的時候就會觸發重平衡
- 訂閱的主題發生變化,當消費者組使用正則表達式訂閱主題,而恰好又新建了對應的主題,就會觸發重平衡
當Kafka觸發重平衡之后,在進行重平衡過程中,Kafka完全處於不可用狀態,消費者無法從Kafka消費消息,如果Kafka節點過多,重平衡持續時間會比較長。
三種重平衡策略
Range
具體實現位於,package org.apache.kafka.clients.consumer.RangeAssignor。
這種分配是基於每個主題的分區分配,如果主題的分區分區不能平均分配給組內每個消費者,那么對該主題,某些消費者會被分配到額外的分區。我們來看看具體的例子。
舉例:目前有兩個消費者C0和C1,兩個主題t0和t1,每個主題三個分區,分別是t0p0,t0p1,t0p2,和t1p0,t1p1,t1p2。
那么分配情況會是:
- C0:t0p0, t0p1, t1p0, t1p1
- C1:t0p2, t1p2
我來大概解釋一下,range這種模式,消費者被分配的單位是基於主題的,拿上面的例子來說,是主題t0的三個分區分配給2個消費者,t1三個分區分配給消費者。於是便會出現消費者c0分配到主題t0兩個分區,以及t1兩個分區的情況(一個主題有三個分區,三個分區無法匹配兩個消費者,勢必有一個消費者分到兩個分區),而非每個消費者分配兩個主題各三個分區。
RoundRobin
具體實現位於,package org.apache.kafka.clients.consumer.RoundRobinAssignor。
RoundRobin是基於全部主題的分區來進行分配的,同時這種分配也是kafka默認的rebalance分區策略。還是用剛剛的例子來看,
舉例:兩個消費者C0和C1,兩個主題t0和t1,每個主題三個分區,分別是t0p0,t0p1,t0p2,和t1p0,t1p1,t1p2。
由於是基於全部主題的分區,那么分配情況會是:
- C0:t0p0, t0p1, t1p1
- C1:t1p0, t0p2, t1p2
因為是基於全部主題的分區來平均分配給消費者,所以這種分配策略能更加均衡得分配分區給每一個消費者。
上面說的都是同一消費者組內消費組都訂閱相同主題的情況。更復雜的情況是,同一組內的消費者訂閱不同的主題,那么任然可能會導致分區不均衡的情況。
還是舉例說明,有三個消費者C0,C1,C2 。三個主題t0,t1,t2,分別有1,2,3個分區 t0p0,t1p0,t1p1,t2p0,t2p1,t2p2。
其中,C0訂閱t0,C1訂閱t0,t1。C2訂閱t0,t1,t2。最終訂閱情況如下:
- C0:t0p0
- C1:t1p0
- C2:t1p1,t2p0,t2p1,t2p2
這個結果乍一看有點迷,其實可以這樣理解,按照序號順序進行循環分配,t0只有一個分區,先碰到C0就分配給它了。t1有兩個分區,被C1和C2訂閱,那么會循環將兩個分區分配出去,最后到t2,有三個分區,卻只有C2訂閱,那么就將三個分區分配給C2。
Sticky
Sticky分配策略是最新的也是最復雜的策略,其具體實現位於package org.apache.kafka.clients.consumer.StickyAssignor。
這種分配策略是在0.11.0才被提出來的,主要是為了一定程度解決上面提到的重平衡非要重新分配全部分區的問題。稱為粘性分配策略。
聽名字就知道,主要是為了讓目前的分配盡可能保持不變,只挪動盡可能少的分區來實現重平衡。
還是舉例說明,有三個消費者C0,C1,C2 。三個主題t0,t1,t2,t3。每個主題各有兩個分區, t0p0,t0p1,t1p0,t1p1,t2p0,t2p1,t3p0,t3p1。
現在訂閱情況如下:
- C0:t0p0,t1p1,t3p0
- C1:t0p1,t2p0,t3p1
- C2:t1p0,t2p1
假設現在C1掛掉了,如果是RoundRobin分配策略,那么會變成下面這樣:
- C0:t0p0,t1p0,t2p0,t3p0
- C2:t0p1,t1p1,t2p1,t3p1
就是說它會全部重新打亂,再分配,而如何使用Sticky分配策略,會變成這樣:
- C0:t0p0,t1p1,t3p0,t2p0
- C2:t1p0,t2p1,t0p1,t3p1
也就是說,盡可能保留了原來的分區情況,不去改變它,在這個基礎上進行均衡分配,不過這個策略目前似乎還有些bug,所以實際使用也不多。
避免重平衡
結合上面說的重平衡觸發條件,分區變化和topic變化大多是人為因素,這種情況不可避免,而消費者組中的消費者掛掉也不是我們能控制的,但是有些時候Kafka會誤認為一個正常的消費者掛掉了,我們可以盡量避免這種情況發生。
當然如果要避免,那首先要知道哪些情況會出現錯誤判斷掛掉的情況。在分布式系統中,通常是通過心跳來維持分布式系統的,kafka也不例外。在分布式系統中,由於網絡問題你不清楚沒接收到心跳,是因為對方真正掛了還是只是因為負載過重沒來得及發生心跳或是網絡堵塞。所以一般會約定一個時間,超時即判定對方掛了。而在kafka消費者場景中,session.timout.ms參數就是規定這個超時時間是多少。
還有一個參數,heartbeat.interval.ms,這個參數控制發送心跳的頻率,頻率越高越不容易被誤判,但也會消耗更多資源。
此外,還有最后一個參數,max.poll.interval.ms,我們都知道消費者poll數據后,需要一些處理,再進行拉取。如果兩次拉取時間間隔超過這個參數設置的值,那么消費者就會被踢出消費者組。也就是說,拉取,然后處理,這個處理的時間不能超過max.poll.interval.ms這個參數的值。這個參數的默認值是5分鍾,而如果消費者接收到數據后會執行耗時的操作,則應該將其設置得大一些。
小結一下,其實主要就是三個參數,session.timout.ms控制心跳超時時間,heartbeat.interval.ms控制心跳發送頻率,以及max.poll.interval.ms控制poll的間隔。這里給出一個相對較為合理的配置,如下:
- session.timout.ms:設置為6s
- heartbeat.interval.ms:設置2s
- max.poll.interval.ms:推薦為消費者處理消息最長耗時再加1分鍾