【四】kafka體系架構之kafka服務端概述(主題、分區、副本、日誌、控制器)

參考

https://www.luozhiyun.com/archives/260

一、架構圖

由此可以看到kafka體系架構的組成有如下幾部分:

1.producer生產者,發送消息到kafka cluster

2.kafka cluster是由broker組成的集羣

3.consumer消費者,從kafka cluster中pull拉取消息進行消費

4.zookeeper cluster,用於保存kafka集羣的元數據
 

這章是介紹kafka cluster部分,即是由broker組成的部分

 二、topic主題、partition分區、replica副本、log日誌

1.topic主題是邏輯上的概念

一個topic代表一類消息。

一個topic有多個partition分區。

創建topic的時候可指定該topic幾個分區,每個分區有幾個副本。

2.partition

一個partition是一個有序的隊列。

topic在物理上的分組,一個topic可以分爲多個partition。

在kafka的日誌目錄中,一個partition就是一個目錄。日誌目錄是由配置文件的log.dirs=配置

partiton命名規則爲topic名稱+有序序號,第一個partiton序號從0開始

3.replica

同一個partition可以有多個副本。

這些Replication之間選出一個Leader,Producer和Consumer只與這個Leader交互,其它Replica作爲Follower從Leader中複製數據。

4.log

不考慮多副本的情況,一個分區對應一個日誌(Log)。

爲了防止 Log 過大,Kafka 又引入了日誌分段(LogSegment)的概念,將 Log 切分爲多個 LogSegment,相當於一個巨型文件被平均分配爲多個相對較小的文件,這樣也便於消息的維護和清理。

而每一個日誌分段中又有3個重要的文件

.log結尾的日誌文件

.index結尾的偏移量索引文件

.timeindex結尾的時間戳索引文件

5.舉例

比如說,kafka集羣有三臺機器node1 node2 node3

有1個主題,topic1 

每個組題有3個分區 topic1-0 topic1-1 topic1-2  

假設分區topic1-0在Node1上,分區topic1-1在Node2上,分區topic1-2在Node3上

log.dirs=/home/kafka_log

那麼在node1的/home/kafka_log目錄下就能看到一個目錄topic1-0

在node2的/home/kafka_log目錄下就能看到一個目錄topic1-1

在node3的/home/kafka_log目錄下就能看到一個目錄topic1-2

在該目錄下能看到有三個文件分別是:

00000000000000000200.index  

00000000000000000200.log 

00000000000000000200.timeindex 

如果我們以3個這樣的文件爲一組叫一個logsegment日誌分段的話,當日志足夠多時,會有多組這樣的logsegment

6.ISR、AR又代表什麼?ISR的伸縮又指什麼

分區中的所有副本統稱爲 AR(Assigned Replicas)。

所有與 leader 副本保持一定程度同步的副本(包括 leader 副本在內)組成ISR(In-Sync Replicas)

ISR 集合是 AR 集合中的一個子集。

ISR的伸縮:
leader 副本負責維護和跟蹤 ISR 集合中所有 follower 副本的滯後狀態,當 follower 副本落後太多或失效時,leader 副本會把它從 ISR 集合中剔除。

如果 OSR 集合中有 follower 副本“追上”了 leader 副本,那麼 leader 副本會把它從 OSR 集合轉移至 ISR 集合。

默認情況下,當 leader 副本發生故障時,只有在 ISR 集合中的副本纔有資格被選舉爲新的 leader,而在 OSR 集合中的副本則沒有任何機會(不過這個原則也可以通過修改相應的參數配置來改變)。

replica.lag.time.max.ms : 這個參數的含義是 Follower 副本能夠落後 Leader 副本的最長時間間隔,當前默認值是 10 秒。

unclean.leader.election.enable:是否允許 Unclean 領導者選舉。開啓 Unclean 領導者選舉可能會造成數據丟失,但好處是,它使得分區 Leader 副本一直存在,不至於停止對外提供服務,因此提升了高可用性。

7.日誌相關的HW、LEO、LSO、LW

HW 是 High Watermark 的縮寫,俗稱高水位,它標識了一個特定的消息偏移量(offset),消費者只能拉取到這個 offset 之前的消息。

LSO是LogStartOffset,一般情況下,日誌文件的起始偏移量 logStartOffset 等於第一個日誌分段的 baseOffset,但這並不是絕對的,logStartOffset 的值可以通過 DeleteRecordsRequest 請求(比如使用 KafkaAdminClient 的 deleteRecords()方法、使用 kafka-delete-records.sh 腳本、日誌的清理和截斷等操作進行修改。

如上圖所示,它代表一個日誌文件,這個日誌文件中有9條消息,第一條消息的 offset(LogStartOffset)爲0,最後一條消息的 offset 爲8,offset 爲9的消息用虛線框表示,代表下一條待寫入的消息。日誌文件的 HW 爲6,表示消費者只能拉取到 offset 在0至5之間的消息,而 offset 爲6的消息對消費者而言是不可見的。

LEO 是 Log End Offset 的縮寫,它標識當前日誌文件中下一條待寫入消息的 offset,上圖中 offset 爲9的位置即爲當前日誌文件的 LEO,LEO 的大小相當於當前日誌分區中最後一條消息的 offset 值加1。分區 ISR 集合中的每個副本都會維護自身的 LEO,而 ISR 集合中最小的 LEO 即爲分區的 HW,對消費者而言只能消費 HW 之前的消息。

LW 是 Low Watermark 的縮寫,俗稱“低水位”,代表 AR 集合中最小的 logStartOffset 值。副本的拉取請求(FetchRequest,它有可能觸發新建日誌分段而舊的被清理,進而導致 logStartOffset 的增加)和刪除消息請求(DeleteRecordRequest)都有可能促使 LW 的增長。

三、Broker

1.Broker處理請求流程

 

在Kafka的架構中,會有很多客戶端向Broker端發送請求,Kafka 的 Broker 端有個 SocketServer 組件,用來和客戶端建立連接,然後通過Acceptor線程來進行請求的分發,由於Acceptor不涉及具體的邏輯處理,非常得輕量級,因此有很高的吞吐量。

接着Acceptor 線程採用輪詢的方式將入站請求公平地發到所有網絡線程中,網絡線程池默認大小是 3個,表示每臺 Broker 啓動時會創建 3 個網絡線程,專門處理客戶端發送的請求,可以通過Broker 端參數 num.network.threads來進行修改。

那麼接下來處理網絡線程處理流程如下:

當網絡線程拿到請求後,會將請求放入到一個共享請求隊列中。Broker 端還有個 IO 線程池,負責從該隊列中取出請求,執行真正的處理。如果是 PRODUCE 生產請求,則將消息寫入到底層的磁盤日誌中;如果是 FETCH 請求,則從磁盤或頁緩存中讀取消息。

IO 線程池處中的線程是執行請求邏輯的線程,默認是8,表示每臺 Broker 啓動後自動創建 8 個 IO 線程處理請求,可以通過Broker 端參數 num.io.threads調整。

Purgatory組件是用來緩存延時請求(Delayed Request)的。比如設置了 acks=all 的 PRODUCE 請求,一旦設置了 acks=all,那麼該請求就必須等待 ISR 中所有副本都接收了消息後才能返回,此時處理該請求的 IO 線程就必須等待其他 Broker 的寫入結果。

2.控制器

在 Kafka 集羣中會有一個或多個 broker,其中有一個 broker 會被選舉爲控制器(Kafka Controller),它負責管理整個集羣中所有分區和副本的狀態。

控制器是如何被選出來的?

Broker 在啓動時,會嘗試去 ZooKeeper 中創建 /controller 節點。Kafka 當前選舉控制器的規則是:第一個成功創建 /controller 節點的 Broker 會被指定爲控制器。

在ZooKeeper中的 /controller_epoch 節點中存放的是一個整型的 controller_epoch 值。controller_epoch 用於記錄控制器發生變更的次數,即記錄當前的控制器是第幾代控制器,我們也可以稱之爲“控制器的紀元”。

controller_epoch 的初始值爲1,即集羣中第一個控制器的紀元爲1,當控制器發生變更時,每選出一個新的控制器就將該字段值加1。Kafka 通過 controller_epoch 來保證控制器的唯一性,進而保證相關操作的一致性。

每個和控制器交互的請求都會攜帶 controller_epoch 這個字段,如果請求的 controller_epoch 值小於內存中的 controller_epoch 值,則認爲這個請求是向已經過期的控制器所發送的請求,那麼這個請求會被認定爲無效的請求。

如果請求的 controller_epoch 值大於內存中的 controller_epoch 值,那麼說明已經有新的控制器當選了。

控制器是做什麼的?

  • 主題管理(創建、刪除、增加分區)

  • 分區重分配

  • Preferred 領導者選舉
    Preferred 領導者選舉主要是 Kafka 爲了避免部分 Broker 負載過重而提供的一種換 Leader 的方案。

  • 集羣成員管理(新增 Broker、Broker 主動關閉、Broker 宕機)
    控制器組件會利用 Watch 機制檢查 ZooKeeper 的 /brokers/ids 節點下的子節點數量變更。目前,當有新 Broker 啓動後,它會在 /brokers 下創建專屬的 znode 節點。一旦創建完畢,ZooKeeper 會通過 Watch 機制將消息通知推送給控制器,這樣,控制器就能自動地感知到這個變化,進而開啓後續的新增 Broker 作業。

  • 數據服務
    控制器上保存了最全的集羣元數據信息。

控制器宕機了怎麼辦?

當運行中的控制器突然宕機或意外終止時,Kafka 能夠快速地感知到,並立即啓用備用控制器來代替之前失敗的控制器。這個過程就被稱爲 Failover,該過程是自動完成的,無需你手動干預。

四、日誌刪除Log Retention

日誌刪除(Log Retention):按照一定的保留策略直接刪除不符合條件的日誌分段。
我們可以通過 broker 端參數 log.cleanup.policy 來設置日誌清理策略,此參數的默認值爲“delete”,即採用日誌刪除的清理策略。

1.基於時間

日誌刪除任務會檢查當前日誌文件中是否有保留時間超過設定的閾值(retentionMs)來尋找可刪除的日誌分段文件集合(deletableSegments)retentionMs 可以通過 broker 端參數 log.retention.hourslog.retention.minuteslog.retention.ms 來配置,

其中 log.retention.ms 的優先級最高,log.retention.minutes 次之,log.retention.hours 最低。

默認情況下只配置了 log.retention.hours 參數,其值爲168,故默認情況下日誌分段文件的保留時間爲7天。

刪除流程:

刪除日誌分段時,

1.首先會從 Log 對象中所維護日誌分段的跳躍表中移除待刪除的日誌分段,以保證沒有線程對這些日誌分段進行讀取操作。

2.然後將日誌分段所對應的所有文件添加上“.deleted”的後綴(當然也包括對應的索引文件)。

3.最後交由一個以“delete-file”命名的延遲任務來刪除這些以“.deleted”爲後綴的文件,這個任務的延遲執行時間可以通過 file.delete.delay.ms 參數來調配,此參數的默認值爲60000,即1分鐘。

2.基於日誌大小

日誌刪除任務會檢查當前日誌的大小是否超過設定的閾值(retentionSize)來尋找可刪除的日誌分段的文件集合(deletableSegments)。
retentionSize 可以通過 broker 端參數 log.retention.bytes 來配置,默認值爲-1,表示無窮大。

注意 log.retention.bytes 配置的是 Log 中所有日誌文件的總大小,而不是單個日誌分段(確切地說應該爲 .log 日誌文件)的大小。

單個日誌分段的大小由 broker 端參數 log.segment.bytes 來限制,默認值爲1073741824,即 1GB。
這個刪除操作和基於時間的保留策略的刪除操作相同。

3.基於日誌起始偏移量

基於日誌起始偏移量的保留策略的判斷依據是某日誌分段的下一個日誌分段的起始偏移量 baseOffset 是否小於等於 logStartOffset,若是,則可以刪除此日誌分段。

 

如上圖所示,假設 logStartOffset 等於25,日誌分段1的起始偏移量爲0,日誌分段2的起始偏移量爲11,日誌分段3的起始偏移量爲23,通過如下動作收集可刪除的日誌分段的文件集合 deletableSegments:

從頭開始遍歷每個日誌分段,日誌分段1的下一個日誌分段的起始偏移量爲11,小於 logStartOffset 的大小,將日誌分段1加入 deletableSegments。
日誌分段2的下一個日誌偏移量的起始偏移量爲23,也小於 logStartOffset 的大小,將日誌分段2加入 deletableSegments。
日誌分段3的下一個日誌偏移量在 logStartOffset 的右側,故從日誌分段3開始的所有日誌分段都不會加入 deletableSegments。
收集完可刪除的日誌分段的文件集合之後的刪除操作同基於日誌大小的保留策略和基於時間的保留策略相同

五、日誌壓縮(Log Compaction)

日誌壓縮(Log Compaction):

針對每個消息的 key 進行整合,對於有相同 key 的不同 value 值,只保留最後一個版本。

如果要採用日誌壓縮的清理策略,就需要將 log.cleanup.policy 設置爲“compact”,並且還需要將 log.cleaner.enable (默認值爲 true)設定爲 true。

如下圖所示,Log Compaction 對於有相同 key 的不同 value 值,只保留最後一個版本。如果應用只關心 key 對應的最新 value 值,則可以開啓 Kafka 的日誌清理功能,Kafka 會定期將相同 key 的消息進行合併,只保留最新的 value 值。

六、底層存儲

1.頁緩存

頁緩存是操作系統實現的一種主要的磁盤緩存,以此用來減少對磁盤 I/O 的操作。具體來說,就是把磁盤中的數據緩存到內存中,把對磁盤的訪問變爲對內存的訪問。

當一個進程準備讀取磁盤上的文件內容時,操作系統會先查看待讀取的數據所在的頁(page)是否在頁緩存(pagecache)中,如果存在(命中)則直接返回數據,從而避免了對物理磁盤的 I/O 操作;如果沒有命中,則操作系統會向磁盤發起讀取請求並將讀取的數據頁存入頁緩存,之後再將數據返回給進程。

同樣,如果一個進程需要將數據寫入磁盤,那麼操作系統也會檢測數據對應的頁是否在頁緩存中,如果不存在,則會先在頁緩存中添加相應的頁,最後將數據寫入對應的頁。被修改過後的頁也就變成了髒頁,操作系統會在合適的時間把髒頁中的數據寫入磁盤,以保持數據的一致性。

用過 Java 的人一般都知道兩點事實:對象的內存開銷非常大,通常會是真實數據大小的幾倍甚至更多,空間使用率低下;Java 的垃圾回收會隨着堆內數據的增多而變得越來越慢。基於這些因素,使用文件系統並依賴於頁緩存的做法明顯要優於維護一個進程內緩存或其他結構,至少我們可以省去了一份進程內部的緩存消耗,同時還可以通過結構緊湊的字節碼來替代使用對象的方式以節省更多的空間。

此外,即使 Kafka 服務重啓,頁緩存還是會保持有效,然而進程內的緩存卻需要重建。這樣也極大地簡化了代碼邏輯,因爲維護頁緩存和文件之間的一致性交由操作系統來負責,這樣會比進程內維護更加安全有效。

2.零拷貝

除了消息順序追加、頁緩存等技術,Kafka 還使用零拷貝(Zero-Copy)技術來進一步提升性能。

所謂的零拷貝是指將數據直接從磁盤文件複製到網卡設備中,而不需要經由應用程序之手。

零拷貝大大提高了應用程序的性能,減少了內核和用戶模式之間的上下文切換。對 Linux 操作系統而言,零拷貝技術依賴於底層的 sendfile() 方法實現。對應於 Java 語言,FileChannal.transferTo() 方法的底層實現就是 sendfile() 方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章