Consumer 加入&離開 Group詳解(九)

圖片看不到可以看我的CSDN的博客:
https://blog.csdn.net/u013332124/article/details/83548706

一、GroupCoordinator 概念

GroupCoordinator是運行在Broker上的一個服務,用來管理Consumer Group的member和各個partition的消費進度,這里的member指的是我們的KafkaConsumer實例。

broker在啟動的時候會啟動一個GroupCoordinator實例。一個集群可能有多個broker,那么怎么確定一個新的Consumer要和哪個broker上的GroupCoordinator交互呢?

這就和kafka上的一個內部使用的topic __consumer_offsets有關系了。

__consumer_offsets

consumer_offsets是kafka內部使用的一個topic,專門用來存儲group具體消費的情況,默認情況下,這個topic有50個partition,每個partition有3個副本。我們進入某個broker的日志目錄,一般都能看到該topic對應的partition目錄,如下圖:

在這里插入圖片描述

Consumer如何找到對應的GroupCoordinator

__consumer_offsets的會分布在各個broker,當一個新的Consumer要尋找和它交互的GroupCoordinator時,需要先對它的GroupId進行hash,然后取模__consumer_offsets的partition數量,最后得到的值就是對應partition,那么這個partition的leader所在的broker就是我們要交互的那個broker了。獲取partition公式如下:

abs(GroupId.hashCode()) % NumPartitions

NumPartitions為__consumer_offsets的數量。GroupId為初始化Consumer時指定的groupId。

舉個例子,假設一個GroupId計算出來的hashcode是5,之后取模50得到5。那么partition-5的leader所在的broker就是我們要找的那個節點。這個Consumer后面都會直接和該broker上的GroupCoordinator交互。

二、Consumer加入Group流程

Consumer在拉取數據之前,必須加入某個group,在consumer加入到group的整個流程中,主要涉及到了3種請求:

  • GROUP_COORDINATOR請求
  • JOIN_GROUP請求
  • SYNC_GROUP請求

GROUP_COORDINATOR請求

前面我們知道了通過__consumer_offsets和對應的公式可以算出要和哪臺broker上的GroupCoordinator做交互,但是我們并不知道__consumer_offsets的各個partition位于哪些broker上。比如我們通過公式算出了要和__consumer_offsets的partition-5所在的broker做交互,但是我們不知道它的partition-5的leader在哪個broker上。因此我們需要先往集群的一個broker發送一個GROUP_COORDINATOR請求來獲取對應的brokerId。

要往哪個broker發送GROUP_COORDINATOR請求也不是隨機選擇的,kafka會默認選擇一個當前連接數最少的broker來發送該請求。這個連接數是指inFightRequest,也就是當前客戶端發往broker還未返回的那些連接數量。

broker處理:

kafka的broker接收到GROUP_COORDINATOR請求后,會通過公式abs(GroupId.hashCode()) % NumPartitions算出對應的partition,然后搜索__consumer_offsets的metadata,找到該partition leader所在的brokerId,最后返回給客戶端。

這里要注意一點:

  1. 如果__consumer_offsets被刪除了或者還未創建,broker找不到對應的metadata時,會自動創建一個新的名為__consumer_offsets的topic然后再查找對應的brokerId。

JOIN_GROUP請求

找到要交互的broker后,客戶端就會往該broker發送 JOIN_GROUP請求了。

JOIN_GROUP請求主要是讓Consumer加入到指定的group中,broker上的GroupCoordinator服務會管理group的各個Consumer。

broker收到 JOIN_GROUP請求后,讓目標group進入 PreparingRebalance狀態,等待一段時間后,返回一些信息,這些信息包括Consumer在group中對應的memberId以及該group的leaderId、generationId(每次reblance都會+1)等等,如果對應consumer是leader,那么還會將當期組中所有的members信息返回給leader用于后面讓leader來分配各個member要消費的partition(第一個加入該group的consumer就是該group的leader)。

Consumer收到broker返回的信息后,如果沒有錯誤則表示已經加入到該Group中了。接著繼續發送SYNC_GROUP請求。

SYNC_GROUP請求

前面的JOIN_GROUP請求只是加入目標group,還沒有真正的分配partiton。SYNC_GROUP請求就是用于獲取consumer要消費哪些partition用的。

Consumer根據前面JOIN_GROUP請求的返回值,會判斷自己是否是leader,如果是leader,就直接獲取group中的所有members然后使用PartitionAssignor的實現類來為group中的各個Consumer分配要消費哪些partition,PartitionAssignor的默認實現是RangeAssignor,也可以通過配置partition.assignment.strategy來指定不同的分配策略。最后leader把分配好的信息封裝成SYNC_GROUP請求發送給broker。

如果consumer是follower,就直接發送一個SYNC_GROUP請求給broker。

broker收到SYNC_GROUP請求后,根據group中的leader給的分配信息在內存中給每個member分配對應的partiton,然后將這些信息返回給對應的consumer。

最后,各個group中的consumer就得到了自己要消費的partition,就可以開始拉取數據了。

三、Group的狀態變更

對于一個 Consumer Group,它會有五種狀態:Dead、Empty、AwaitingSync、PreparingRebalance、Stable。狀態間的變更關系如下圖所示:

[圖片上傳失敗...(image-f69012-1542368543646)]

Empty狀態

Empty狀態表示該Consumer Group中沒有任何member。新建的Group都是處于這個狀態,它可能轉化為以下兩種狀態

  • PreparingRebalance:如果有新的member加入,狀態就會變更成PreparingRebalance,等待partition-rebalance開始。
  • Dead:如果該group被移除掉,狀態就會變成Dead。

PreparingRebalance狀態

PreparingRebalance狀態表示該Group正在等待partition-rebalance開始。這個狀態存在的目的主要是為了等待所有的member都加入到該Group中了,然后開始進行partition rebalance(也就是進入AwaitingSync狀態)。這樣就可以盡量保證在進行partition reblance時,group中的member不會發送變動。它可能轉化為以下三種狀態:

  • Dead:如果該group被移除掉,狀態就會變成Dead
  • AwaitingSync:第一個發送PreparingRebalance請求的Consumer返回后,group的狀態就會變成AwaitingSync,等待重新分配partition
  • Empty:group中最后一個member離開了,group重新變為Empty狀態

AwaitingSync狀態

AwaitingSync狀態表示該Group正在等待重新分配partition的結果,partiton的分配是由member的leader來進行的,等leader發來SYNC_GROUP請求,GroupCoordinator知道partiton的分配情況了,Group狀態就會變成Stable。它可能轉化為以下三種狀態:

  • Dead:如果該group被移除掉,狀態就會變成Dead
  • PreparingRebalance:如果有新的member加入或者舊成員離開,狀態會重新變回PreparingRebalance,等待新的一輪partition分配
  • Stable:partition分配完成,進入Stable狀態

Stable狀態

Stable狀態表示目前Group已經給各個Consumer分配好各自要消費的partition了。只要Group沒有發生成員變動或者member要消費的元數據沒發送變動(比如某topic的partition數量變更),狀態就會一直維持在Stable。它也可能轉化為以下兩種狀態:

  • Dead:如果該group被移除掉,狀態就會變成Dead
  • PreparingRebalance:如果有新的member加入或者舊成員離開,狀態會重新變回PreparingRebalance,等待新的一輪partition分配

Dead狀態

Dead狀態表示該Group已經被移除掉了。如果__consumer_offsets的partition分布發生變動,就會導致Group可能不屬于該broker上的GroupCoordinator管理,GroupCoordinator就會移除Group。

正常consumer加入group中的狀態變動情況

Empty —> PreparingRebalance —> AwaitingSync —> Stable

當一個consumer發送 JOIN_GROUP請求要求加入一個新的group時,GroupCoordinator發現之前沒有這個group,就會新建一個group,此時該group的狀態為Empty。之后由于有新成員加入,狀態迅速轉變為PreparingRebalance。另外,GroupCoordinator收到JOIN_GROUP請求后會等待一段時間再返回,讓該Group在PreparingRebalance狀態等待一定時間,以確保該加入的member都加入了。

PreparingRebalance再返回JOIN_GROUP請求后,就會把Group的狀態置為AwaitingSync。 Consumer收到響應后,會再發送SYNC_GROUP請求等待partition分配完成。如果該Consumer是leader,則該Consumer會在本地進行partition的分配,然后把partition的分配結果隨著SYNC_GROUP請求一起上報給GroupCoordinator。之后GroupCoordinator收到leader發送過來的分配情況,就會將狀態置為Stable,之后將這些信息作為SYNC_GROUP請求的響應發送給各個Consumer,各個Consumer就都得到了自己要消費的partition。

四、Consumer心跳機制

Consumer在加入Group后,會開啟一個線程,不斷的向GroupCoordinator發送心跳請求,報告自己還活著。GroupCoordinator會管理group中所有Consumer的心跳,如果發現有一個Consumer超過一定時間沒有發送心跳過來,GroupCoordinator會認為這個Consumer已經離開group。這時GroupCoordinator會將該group的狀態重新置為PreparingRebalance,開啟新一輪的partition分配。

心跳的發送頻率和consumer的配置 heartbeat.interval.ms有關,默認是3000,也就是每3s發送一次心跳。

GroupCoordinator判斷member是否過期和consumer的配置session.timeout.ms有關,默認為10000,也就是超過10s沒收到心跳請求,就移除該member。

Group處于Stable狀態下,新加入一個Consumer

如果目前group已經處于stable狀態了(各個consumer都在消費了),又新加入了一個Consumer,那么狀態會怎么變更呢?

首先,新的Consumer會發送一個 JOIN_GROUP請求給GroupCoordinator,GroupCoordinator收到請求后發現這是一個新的member,就會將group的狀態置為PreparingRebalance,然后等待其他member也發送 JOIN_GROUP請求。

那么其他正在消費的consumer怎么知道要重新分配partition呢?這個就和心跳機制有關系了。Consumer發送心跳給GroupCoordinator的時候,如果GroupCoordinator發現此刻group的狀態是PreparingRebalance,就會告訴Consumer需要重新分配partition了,各個Consumer收到消息后就開始重新發送JOIN_GROUP請求。

Consumer離開可能引發的Group狀態變更

當Consumer超過一定時間沒有發送心跳,GroupCoordinator會認為該Consumer已經離開group。此時GroupCoordinator會將該group的狀態置為PreparingRebalance,等待新一輪的parition分配。

五、 __consumer_offsets topic中的消息

在之前老的版本中,consumer消費的offset情況是存儲在zookeeper中的,但是kafka對zk的依賴性很強,當consumer的數量不斷增多,zk的負擔也會越來越大,最終可能會影響到zk的正常運行。因此,在后面的版本中,kafka設計者將consumer的commitedoffset寫到內部的一個topic中,也就是__consumer_offsets。

__consumer_offsets存儲的兩種消息類型

除了存儲Consumer Group的commitedOffset外, __consumer_offsets中其他還存儲了另外一種消息類型:GroupCoordinator管理的元數據信息,這些元數據包括GroupCoordinator管理的所有Group的信息,比如Group的狀態,Group的leader信息,以及Group的各個member信息,但不包括Group中各個topic-partition的commitedOffset。kafka通過消息key的不同來區分兩種消息類型

  1. commitedOffset 消息類型

它的key是 GroupId+topic+partition,value是對應的offset。

  1. GroupCoordinator 消息類型

它的key是 GroupId,value是對應的元數據信息

__consumer_offsets 消息的加載和寫入

數據加載

[圖片上傳失敗...(image-14e902-1542368543647)]

為了讀取更快,無論是commitedOffset,還是GroupCoordinator的元數據,都會從 __consumer_offsets中加載出來緩存起來。這些數據在broker啟動的時候加載的。

一個broker在啟動的時候,并不會知道自己機器上的那些partition是leader還是replica,所以無法立即從 __consumer_offsets中加載數據。如果這時有Consumer來拉取offset,kafka就會拋出一個異常給Consumer,Consumer等待會等待若干時間后再次請求。

在broker啟動后,Controller會感應到有新的broker啟動,然后知道這個broker上的partition哪些是leader,哪些是replica,之后發送一個LEADER_AND_ISR請求給該broker。該Broker收到這個請求,解析附帶的請求數據,就可以知道自己機器上的parition哪些是leader,哪些是replica了。接著,如果發現這些partition有__consumer_offsets的話,就開始讀取__consumer_offsets的數據并加載的內存中。

加載的流程也很簡單,kafka會預先申請5M的內存空間,然后從目標partition的第一條offset開始讀取,直到讀取到最后一個offset為止。由于kafka會定時對 __consumer_offsets進行compact,因此__consumer_offsets的partition大小一般也不會太大。

commitedOffset消息寫入

當有Consumer提交OFFSET_COMMIT請求時,就會往__consumer_offsets的對應partition寫入消息了。由于kafka對這個topic開始了消息壓縮(Compact),因此隨著時間的流逝,相同的key,舊的記錄會被清除掉,只剩下最新的那個key

GroupCoordinator元數據寫入

在Group狀態變為Stable后,GroupCoordinator會將當前Group的相關元數據寫入對應partiiton。當有成員離開Group,Group狀態變成Empty的時候,也會寫一條消息到partition。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 姓名:周小蓬 16019110037 轉載自:http://blog.csdn.net/YChenFeng/art...
    aeytifiw閱讀 34,741評論 13 425
  • Kafka簡介 Kafka是一種分布式的,基于發布/訂閱的消息系統。主要設計目標如下: 以時間復雜度為O(1)的方...
    Alukar閱讀 3,096評論 0 43
  • 本文轉載自http://dataunion.org/?p=9307 背景介紹Kafka簡介Kafka是一種分布式的...
    Bottle丶Fish閱讀 5,491評論 0 34
  • 都說畢業季是分手季。前幾天,在于朋友聊天的過程中,提到過畢業后去哪發展的問題。她跟我說,她會留在北方,因為她的家就...
    鑫愛敏閱讀 705評論 0 0
  • 我們相隔相隔宇宙 中間有一萬顆星球 一半是你的禁地 一半是我的囚籠 我們相隔兩片天空 中間有一萬道彩虹 一半在你身...
    仰望星空得草閱讀 245評論 0 1