一、Kafka 資料日誌
1.主題 Topic
Topic 是邏輯概念。
主題類似於分類,也可以理解為一個訊息的集合。每一條傳送到 Kafka 的訊息都會帶上一個主題資訊,表明屬於哪個主題。
Kafka 的主題是支援多使用者訂閱的,即一個主題可以有零個、一個或者多個消費者來訂閱該主題的訊息。
2.分割槽 Partition
1)分割槽原因
- 方便叢集擴充套件,因為一個 Topic 由多個 Partition 組成,而 Partition 又可以通過調整以適應不同的機器,因而整個叢集就可以適應任意大小的資料;
- 方便提高併發,因為可以以 Partition 為單位進行讀寫。
2)分割槽概念
Partition 是物理概念,一個 Partition 就對應於一個資料夾。
在 Kafka 中每個主題都可以劃分成多個分割槽,每個主題至少有一個分割槽,同一個主題的分割槽之間所包含的訊息是不一樣的。每個分割槽只會存在於一個 Broker 上。
每個訊息在被新增到分割槽中的時候,都會帶一個 offset(偏移量),它是訊息在分割槽中的編號。通過設定 offset,Kafka 可以保證分割槽內的訊息是有序的,但跨分割槽的訊息是無序的。
3)分割槽策略
- 在指明 Partition 的情況下,直接將指明的值作為 Partition 的值;
- 在未指明 Partition 但有 key 的情況下,將 key 的雜湊值和該 Topic 中 Partition 的數量進行求餘操作,其結果作為 Partition 的值;
- 在未指明 Partition 的值也沒有 key 的情況下,第一次呼叫時生成一個隨機整數,之後在這個整數上自增,將其值和該 Topic 中 Partition 的數量進行求餘操作,其結果作為 Partition 的值,即 round-robin 演算法。round-robin:輪詢排程演算法,原理是每一次把來自使用者的請求輪流分配給內部中的伺服器,從1開始,直到 N(內部伺服器個數),然後重新開始迴圈。
3.資料日誌
對於每一個主題,Kafka 保持一個分割槽日誌檔案 log,而生產者生產的訊息會不斷新增到 log 檔案末尾,為了防止 log 檔案過大導致效率降低,Kafka 採用了分片和索引的機制,每個 Partition 分為多個 Segment,每個 Segment 又由兩個檔案組成 “.index”索引檔案和“.log”資料檔案。這些檔案位於一個資料夾下,資料夾的命名規則為:Topic 名稱+ Partition 序號,而 index 和 log 檔案的命名是根據當前 Segment 的第一條訊息的 offset 偏移量來命名的。
在 index 檔案中,是使用 key-value 結構來儲存的,key 是 Message 在 log 檔案中的編號,value 就是訊息值。但是 Index 檔案中並沒有給每條訊息都建立索引,而是採用了稀疏儲存的方式,每隔一定位元組的資料建立一條索引。這樣避免了索引檔案佔用過多的空間,從而可以將索引檔案保留在記憶體中 。
二、Kafka 副本機制
1.副本 Replica
Replica 是 Partition 的副本。每個 Partition 可以有多個副本,這多個副本之中,只有一個是 leader,其他都是 follower,所有的讀寫請求都通過 leader 來完成,follower 只負責備份資料。follow 會均勻分配到多個 Broker 上,當 leader 所在的機器掛掉之後,會從 follower 中重新選出一個副本作為 leader 繼續提供服務。
那麼為什麼有這麼多的副本卻只使用 leader 來提供服務呢?如果都用上不是可以提高效能嗎?這其實就是一致性和可用性之間的取捨了,如果多個副本同時進行讀寫操作,很容易出現問題。例如在讀取訊息時,一個消費者沒有讀取到,而另一個消費者卻讀取到了,因為這個消費者讀取的是 leader 副本,顯然這樣是不行的。所以為了保證一致性,只使用一個 leader 副本來提供服務。
2.Commit 策略
Commit:指 leader 通知 Producer ,資料已經成功收到。Kafka 儘量保證 Commit 後即使 leader 掛掉,其他 flower 都有該條資料。
我們已經知道一個 Partition 可以有多個副本,當有資料寫入 leader 時,這些 follower 是怎麼複製資料的?下面是兩種策略:
- 同步複製:只有所有的 follower 把資料都複製之後才 Commit,這種策略的一致性好,但可用性不高,當 follower 數量較多時,複製過程就會很耗時。
- 非同步複製:只要 leader 拿到資料就立即 Commit,然後 follower 再複製,這種策略的可用性高,但一致性較差,例如在 Commit 之後 leader 掛掉,其他 follow 中就都沒有該資料了。
那麼 Kafka 使用的是什麼策略呢?Kafka 使用了一種 ISR (in-sync Replica)機制:
- leader 會維護一個與其基本保持同步的 Replica 列表,稱為 ISR(in-sync Replica);
- 每個 Partition 都會有一個 ISR ,而且是由 leader 動態維護;
- 如果 ISR 中有一個 flower 比一個 leader 落後太多,或者超過一定時間沒有請求複製資料,則將其移除;
- 當 ISR 中所有 Replica 都向 leader 傳送 ACK 時,leader 才會 Commit。
3.ACK 機制
ACK:為了保證 Producer 傳送的訊息能夠到達指定的 Topic,每個 Partition 收到訊息之後要向 Producer 傳送 ACK(acknowledgement)確認收到,如果 Producer 收到了 ACK,就會進行下一輪的傳送,否則重新傳送訊息。
對於某些不太重要的資料,對資料的完整性沒有嚴格的要求,可以容忍少量資料的丟失,Kafka 就沒有必要等 ISR 中的所有 follower 都複製完,所以 Kafka 提供了三種可靠性級別,供使用者進行選擇和配置:
- 0:Producer 不用等待接收 ACK,只要資料接收到就返回,此時提供了最低的延遲,但若出現故障,會丟失資料;
- 1:Producer 等待 ACK,leader 接收成功後傳送 ACK,丟失資料會重發,但若 follower 同步前 leader 掛掉了,會丟失資料;
- -1:Producer 等待 ACK,leader 接收到資料,follower 同步完資料後傳送 ACK,但若 follower 同步完,而 ACK 還沒傳送 leader 就掛掉了,會出現資料重複。
三、Kafka 消費策略
1.消費者組 Consumer Group
Consumer Group,消費者組,一個消費者組包含多個消費者,這些消費者共用一個 ID,即 Group ID,組內的消費者協調消費所訂閱的主題的所有分割槽,當然了,每個分割槽只能由一個消費者進行消費。
2.偏移量 offset
offset ,偏移量,記錄的是消費位置資訊,即在消費者消費資料的過程中,已經消費了多少資料。
要獲取 offset 的資訊,需要知道消費者組的 ID 、主題和分割槽 ID。在老版本的 Kafka 中(0.9以前),offset 資訊是儲存在 ZooKeeper 中的,目錄結構如下:
/consumers/<groupId>/offsets/<topic>/<partitionId>
但在新版本中(0.9及其之後的版本),Kafka 增加了一個主題:__consumers_offsets,將 offset 資訊寫入這個 Topic。不過這個 Topic 預設是不使用的,若要使用需要修改相應的設定,需要在 consumer.properties 中設定:
exclude.internal.topics=false
查詢 __consumer_offsets Topic 中的所有內容,如果是0.11.0.0之前的版本,使用如下命令:
bin/kafka-console-consumer.sh --topic __consumer_offsets --zookeeper localhost:2181 --formatter "kafka.coordinator.GroupMetadataManager\$OffsetsMessageFormatter" --consumer.config config/consumer.properties --from-beginning
如果是0.11.0.0及其之後的版本,使用如下命令:
bin/kafka-console-consumer.sh --topic __consumer_offsets --zookeeper localhost:2181 --formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter" --consumer.config config/consumer.properties --from-beginning
3.消費策略
1)RangeAssignor
Kafka 預設的消費策略。
RangeAssignor 策略的原理是按照消費者總數和分割槽總數進行整除運算來獲得一個跨度,然後將分割槽按照跨度進行平均分配,以保證分割槽儘可能均勻地分配給所有的消費者。對於每一個 Topic,RangeAssignor 策略會將消費組內所有訂閱這個 Topic 的消費者按照名稱的字典序排序,然後為每個消費者劃分固定的分割槽範圍,如果不夠平均分配,那麼字典序靠前的消費者會被多分配一個分割槽。
假設消費組內有2個消費者 C1 和 C2,都訂閱了主題 Topic1 和 Topic2,並且每個主題都有3個分割槽,那麼所訂閱的所有分割槽可以標識為:T10、T11、T12、T20、T21、T22。最終的分配結果為:
可以看到這樣分配已經不均了,如果將類似的情況擴大,可能出現超出部分消費者消費能力的情況。
2)RoundRobinAssignor
RoundRobinAssignor 策略的原理是將消費組內所有消費者以及消費者所訂閱的所有 Topic 的 Partition 按照字典序排序,然後通過輪詢方式逐個將分割槽以此分配給每個消費者。
假設有3個主題 Topic1、Topic2、Topic3,分別有1、2、3個分割槽,那麼所訂閱的所有分割槽可以標識為:T10、T20、T21、T30、T31、T32。消費組內有3個消費者 C1、C2、C3,其中 C1 訂閱了 Topic1,C2 訂閱了 Topic1 和 Topic2,C3 訂閱了 Topic1、Topic2 和 Topic3。最終的分配結果為:
可以看到該策略也不是十分完美,這樣分配其實並不是最優解,因為完全可以將分割槽 T10 和 T11 分配給消費者 C1。
3)StickyAssignor
Kafka 從0.11.x 版本開始引入這種分配策略。
StickyAssignor 策略的分配要儘可能達到兩個目的:1是分割槽的分配要儘可能均勻,2是分割槽的分配情況要儘可能和上次保持一樣。分配情況儘可能保持一樣,就好像單詞“sticky”,使得分配策略具有“粘性”。
假設有3個主題 Topic1、Topic2、Topic3,分別有1、2、3個分割槽,那麼所訂閱的所有分割槽可以標識為:T10、T20、T21、T30、T31、T32。消費組內有3個消費者 C1、C2、C3,其中 C1 訂閱了 Topic1,C2 訂閱了 Topic1 和 Topic2,C3 訂閱了 Topic1、Topic2 和 Topic3。最終的分配結果為:
可以看到這是一個最優解,而如果此時消費者 C1 從消費者組中移除,分配結果會變為:
和上次的分配情況相比,其中五個分割槽的分配情況都沒有改變。