深刻分析Kafka架構(三):消費者消費方式、三種分區分配策略、offset維護

1、前言

在以前的兩篇文章裏,咱們分析了kafka的工做流程、存儲機制、分區策略、數據可靠性、故障處理。從而弄清楚了kafka的總體架構以及生產者生產的數據是怎麼存儲,保證可靠性以及遇到故障時進行處理的。算法

深刻分析Kafka架構(一):工做流程、存儲機制、分區策略apache

深刻分析Kafka架構(二):數據可靠性、故障處理網絡

那麼接下來,咱們將分析kafka架構裏的消費者是如何工做的,本文將重點分析kafka消費者的消費方式,三種分區分配策略(Range分配策略、RoundRobin分配策略、Sticky分配策略) 以及offset的維護。架構

2、消費者消費方式

先說結論:消費者採用pull(拉)模式從broker中讀取數據。學習

爲何不採用push(推,填鴨式教學)的模式給消費者數據呢?首先回想下我們上學學習不就是各類填鴨式教學嗎?無論你三七二十一,就是按照教學進度給你灌輸知識,能不能接受是你的事,並美其名曰:優勝略汰!spa

這種push方式在kafka架構裏顯然是不合理的,好比一個broker有多個消費者,它們的消費速率不一樣,一昧的push只會給消費者帶來拒絕服務以及網絡擁塞等風險。而kafka顯然不可能去放棄速率低的消費者,所以kafka採用了pull的模式,能夠根據消費者的消費能力以適當的速率消費broker裏的消息。.net

固然讓消費者去pull數據天然也是有缺點的。一樣聯想上學的場景,若是把學習主動權所有交給學生,那有些學生想學的東西老師那裏沒有怎麼辦?那他不就陷入了一生就在那不斷求索,然而別的也啥都學的這個死循環的狀態了。kafka也是這樣,採用pull模式後,若是kafka沒有數據,消費者可能會陷入循環中,一直返回空數據。爲了解決這個問題,Kafka消費者在消費數據時會傳入一個時長參數timeout,若是當前沒有數據可供消費,消費者會等待一段時間以後再返回,這段時長即爲timeout。線程

3、分區分配策略

咱們在第一篇文章裏分析了kafka存儲數據的分區策略,這裏對於消費者來講,一個consumer group中有多個consumer,一個 topic有多個partition,因此確定會涉及到partition的分配問題,即肯定每一個partition由哪一個consumer來消費,這就是分區分配策略(Partition Assignment Strategy)。翻譯

3.一、分配分區的前提條件

首先kafka設定了默認的消費邏輯:一個分區只能被同一個消費組(ConsumerGroup)內的一個消費者消費。設計

在這個消費邏輯設定下,假設目前某消費組內只有一個消費者C0,訂閱了一個topic,這個topic包含6個分區,也就是說這個消費者C0訂閱了6個分區,這時候可能會發生下列三種狀況:

  1. 若是這時候消費者組內新增了一個消費者C1,這個時候就須要把以前分配給C0的6個分區拿出來3個分配給C1;
  2. 若是這時候這個topic多了一些分區,就要按照某種策略,把多出來的分區分配給C0和C1;
  3. 若是這時候C1消費者掛掉了或者退出了,不在消費者組裏了,那全部的分區須要再次分配給C0。

總結一下,這三種狀況其實就是kafka進行分區分配的前提條件:

  1. 同一個 Consumer Group 內新增消費者;
  2. 訂閱的主題新增分區;
  3. 消費者離開當前所屬的Consumer Group,包括shuts down 或 crashes。

只有知足了這三個條件的任意一個,纔會進行分區分配 。分區的全部權從一個消費者移到另外一個消費者稱爲從新平衡(rebalance),如何rebalance就涉及到本節提到的分區分配策略。kafka提供了消費者客戶端參數partition.assignment.strategy用來設置消費者與訂閱主題之間的分區分配策略。默認狀況下,此參數的值爲:org.apache.kafka.clients.consumer.RangeAssignor,即採用range分配策略。除此以外,Kafka中還提供了roundrobin分配策略和sticky分區分配策略。消費者客戶端參數partition.asssignment.strategy能夠配置多個分配策略,把它們以逗號分隔就能夠了。

3.二、Range分配策略

Range分配策略是面向每一個主題的,首先會對同一個主題裏面的分區按照序號進行排序,並把消費者線程按照字母順序進行排序。而後用分區數除以消費者線程數量來判斷每一個消費者線程消費幾個分區。若是除不盡,那麼前面幾個消費者線程將會多消費一個分區。

咱們假設有個名爲T1的主題,包含了7個分區,它有兩個消費者(C0和C1),其中C0的num.streams(消費者線程) = 1,C1的num.streams = 2。排序後的分區是:0,1,2,3,4,5,6;消費者線程排序後是:C0-0,C1-0,C1-1;一共有7個分區,3個消費者線程,進行計算7/3=2…1,商爲2餘數爲1,則每一個消費者線程消費2個分區,而且前面1個消費者線程多消費一個分區,結果會是這樣的:

消費者線程 對應消費的分區序號
C0-0 0,1,2
C1-0 3,4
C1-1 5,6

這樣看好像還沒什麼問題,可是通常在我們實際生產環境下,會有多個主題,咱們假設有3個主題(T1,T2,T3),都有7個分區,那麼按照我們上面這種Range分配策略分配後的消費結果以下:

消費者線程 對應消費的分區序號
C0-0 T1(0,1,2),T2(0,1,2),T3(0,1,2)
C1-0 T1(3,4),T2(3,4),T3(3,4)
C1-1 T1(5,6),T2(5,6),T3(5,6)

咱們能夠發現,在這種狀況下,C0-0消費線程要多消費3個分區,這顯然是不合理的,其實這就是Range分區分配策略的缺點。

3.三、RoundRobin分配策略

RoundRobin策略的原理是將消費組內全部消費者以及消費者所訂閱的全部topic的partition按照字典序排序,而後經過輪詢算法逐個將分區以此分配給每一個消費者。

使用RoundRobin分配策略時會出現兩種狀況:

  1. 若是同一消費組內,全部的消費者訂閱的消息都是相同的,那麼 RoundRobin 策略的分區分配會是均勻的。
  2. 若是同一消費者組內,所訂閱的消息是不相同的,那麼在執行分區分配的時候,就不是徹底的輪詢分配,有可能會致使分區分配的不均勻。若是某個消費者沒有訂閱消費組內的某個 topic,那麼在分配分區的時候,此消費者將不會分配到這個 topic 的任何分區。

咱們分別舉例說明:

第一種:好比咱們有3個消費者(C0,C1,C2),都訂閱了2個主題(T0 和 T1)而且每一個主題都有 3 個分區(p0、p一、p2),那麼所訂閱的全部分區能夠標識爲T0p0、T0p一、T0p二、T1p0、T1p一、T1p2。此時使用RoundRobin分配策略後,獲得的分區分配結果以下:

消費者線程 對應消費的分區序號
C0 T0p0、T1p0
C1 T0p一、T1p1
C2 T0p二、T1p2

能夠看到,這時候的分區分配策略是比較平均的。

第二種:好比咱們依然有3個消費者(C0,C1,C2),他們合在一塊兒訂閱了 3 個主題:T0、T1 和 T2(C0訂閱的是主題T0,消費者C1訂閱的是主題T0和T1,消費者C2訂閱的是主題T0、T1和T2),這 3 個主題分別有 一、二、3 個分區(即:T0有1個分區(p0),T1有2個分區(p0、p1),T2有3個分區(p0、p一、p2)),即整個消費者所訂閱的全部分區能夠標識爲 T0p0、T1p0、T1p一、T2p0、T2p一、T2p2。此時若是使用RoundRobin分配策略,獲得的分區分配結果以下:

消費者線程 對應消費的分區序號
C0 T0p0
C1 T1p0
C2 T1p一、T2p0、T2p一、T2p2

這時候顯然分配是不均勻的,所以在使用RoundRobin分配策略時,爲了保證得均勻的分區分配結果,須要知足兩個條件:

  1. 同一個消費者組裏的每一個消費者訂閱的主題必須相同;
  2. 同一個消費者組裏面的全部消費者的num.streams必須相等。

若是沒法知足,那最好不要使用RoundRobin分配策略。

3.四、Sticky分配策略

最後介紹一下Sticky分配策略,這種分配策略是在kafka的0.11.X版本纔開始引入的,是目前最複雜也是最優秀的分配策略。

Sticky分配策略的原理比較複雜,它的設計主要實現了兩個目的:

  1. 分區的分配要儘量的均勻;
  2. 分區的分配儘量的與上次分配的保持相同。

若是這兩個目的發生了衝突,優先實現第一個目的。

咱們舉例進行分析:好比咱們有3個消費者(C0,C1,C2),都訂閱了2個主題(T0 和 T1)而且每一個主題都有 3 個分區(p0、p一、p2),那麼所訂閱的全部分區能夠標識爲T0p0、T0p一、T0p二、T1p0、T1p一、T1p2。此時使用Sticky分配策略後,獲得的分區分配結果以下:

消費者線程 對應消費的分區序號
C0 T0p0、T1p0
C1 T0p一、T1p1
C2 T0p二、T1p2

哈哈,這裏可能會驚呼,怎麼和前面RoundRobin分配策略同樣,其實底層實現並不同。這裏假設C2故障退出了消費者組,而後須要對分區進行再平衡操做,若是使用的是RoundRobin分配策略,它會按照消費者C0和C1進行從新輪詢分配,再平衡後的結果以下:

消費者線程 對應消費的分區序號
C0 T0p0、T0p二、T1p1
C1 T0p一、T1p0、T1p2

可是若是使用的是Sticky分配策略,再平衡後的結果會是這樣:

消費者線程 對應消費的分區序號
C0 T0p0、T1p0、T0p2
C1 T0p一、T1p一、T1p2

看出區別了嗎?Stiky分配策略保留了再平衡以前的消費分配結果,並將原來消費者C2的分配結果分配給了剩餘的兩個消費者C0和C1,最終C0和C1的分配還保持了均衡。這時候再體會一下sticky(翻譯爲:粘粘的)這個詞彙的意思,是否是豁然開朗了。

爲何要這麼處理呢?

這是由於發生分區重分配後,對於同一個分區而言有可能以前的消費者和新指派的消費者不是同一個,對於以前消費者進行到一半的處理還要在新指派的消費者中再次處理一遍,這時就會浪費系統資源。而使用Sticky策略就可讓分配策略具有必定的「粘性」,儘量地讓先後兩次分配相同,進而能夠減小系統資源的損耗以及其它異常狀況的發生。

接下來,再來看一下上一節RoundRobin存在缺陷的地方,這種狀況下sticky是怎麼分配的?

好比咱們依然有3個消費者(C0,C1,C2),他們合在一塊兒訂閱了 3 個主題:T0、T1 和 T2(C0訂閱的是主題T0,消費者C1訂閱的是主題T0和T1,消費者C2訂閱的是主題T0、T1和T2),這 3 個主題分別有 一、二、3 個分區(即:T0有1個分區(p0),T1有2個分區(p0、p1),T2有3個分區(p0、p一、p2)),即整個消費者所訂閱的全部分區能夠標識爲 T0p0、T1p0、T1p一、T2p0、T2p一、T2p2。此時若是使用sticky分配策略,獲得的分區分配結果以下:

消費者線程 對應消費的分區序號
C0 T0p0
C1 T1p0、T1p1
C2 T2p0、T2p一、T2p2

因爲C0消費者沒有訂閱T1和T2主題,所以如上這樣的分配策略已是這個問題的最優解了!

這時候,再補充一個例子,加入C0掛了,發生再平衡後的分配結果,RoundRobin和Sticky又有什麼區別呢?

RoundRobin再平衡後的分配狀況:

消費者線程 對應消費的分區序號
C1 T0p0、T1p1
C2 T1p0、T2p0、T2p一、T2p2

而若是使用Sticky策略,再平衡後分分配狀況:

消費者線程 對應消費的分區序號
C1 T1p0、T1p一、T0p0
C2 T2p0、T2p一、T2p2

這裏咱們驚奇的發現sticky只是把以前C0消耗的T0p0分配給了C1,咱們結合資源消耗來看,這相比RoundRobin能節省更多的資源。

所以,強烈建議使用sticky分區分配策略。

4、offset維護

在現實狀況下,消費者在消費數據時可能會出現各類會致使宕機的故障問題,這個時候,若是消費者後續恢復了,它就須要從發生故障前的位置開始繼續消費,而不是從頭開始消費。因此消費者須要實時的記錄本身消費到了哪一個offset,便於後續發生故障恢復後繼續消費。Kafka 0.9版本以前,consumer默認將offset保存在Zookeeper中,從0.9版本開始,consumer默認將offset保存在Kafka一個內置的topic中,該topic爲 __consumer_offsets

offset的維護很簡單,之因此單獨列出來,是由於offset維護針對不一樣的kafka版本進行的處理是不一樣的,這點須要注意。

5、總結

本文咱們分析了Kafka的消費者消費方式、分區分配策略、offset維護,其中分區分配策略是重點,咱們很是詳細的舉例對比說明了Range,RoundRobin,Sticky這三種策略的優缺點,相信讀完必定會有所收穫。

到這裏kafka架構已經分析完畢,這三篇文章系統性的從總體工做流程,存儲機制,分區策略入手,到生產者生產數據,如何保證數據可靠性,遇到故障如何處理,到最後的消費者如何去消費數據,如何把分區分配給消費者等問題都能找到解釋。

因本人能力有限,若是對kafka架構的分析有誤,還請您不吝賜教,謝謝。

相關文章
相關標籤/搜索