集群的成員關系
Kafka使用Zookeeper維護集群的成員信息。
- 每一個
broker
都有一個唯一的標識,這個標識可以在配置文件中指定,也可以自動生成。 - 當
broker
在啟動時通過創建Zookeeper的臨時節點把自己的ID注冊到Zookeeper中。broker、控制器和其他一些動態系統工具會訂閱Zookeeper的/brokers/ids
路徑;當有broker
加入或退出集群時,會收到通知。 - 當試圖啟動另一個具有相同ID的
broker
時,會收到錯誤信息。
控制器
控制器也是一個
broker
,除了提供一般broker功能外,還負責選舉分區首領。
創建控制器
- 集群中第一個啟動的broker會通過Zookeeper創建一個
/controller
的臨時節點讓自己成為控制器; - Zookeeper會為控制器分配一個
epoch
。 - 其他
broker
在啟動時,也會嘗試創建,但是因為已經存在他們會收到”節點已存在“異常; - 然后在控制器節點上創建
Zookeeper watch
,這樣就可以接收這個節點的變更通知。通過這樣的方式來保證節點只有一個控制器。
變更控制器
- 控制器關閉或者與Zookeeper斷開連接,這個臨時節點會消失;
- 當其他節點收到控制器節點消失的通知時,會嘗試創建
/controller
的臨時節點成為控制節點; - 其他未創建成功的broker會在新的控制節點上創建
Zookeeper watch
, - 新的控制器節點由Zookeeper分配一個數值更大的
epoch
。這樣做的目的是為了杜絕之前離線的控制器重新上線,并且發送消息,如果broker接收到消息的epoch
小于監聽的則會忽略當前消息。
新控制器 KRaft
用基于
Raft
的控制器替換基于Zookeeper的控制器。
集群即可以使用基于Zookeeper
的傳統控制器,也可以使用KRaft
。
為什么替換控制器
- 元數據是同步寫入Zookeeper的,但是異步發送給
broker
的,Zookeeper的接收更新也是異步的,會導致broker、控制器和Zookeeper之間元數據不一致的情況 - 控制器在重新啟動時需要從Zookeeper讀取所有的broker和分區元數據,再將他們發給所有broker,隨著分區和broker的爭奪,重啟控制器會變慢。
- 元數據所有權架構不夠好,有些操作通過控制器、有些通過broker、有些通過Zookeeper來完成
- 使用Kafka需要對Zookeeper有一定了解,學習成本較高
Zookeeper主要功能
- 用于選舉控制器
- 保存集群元數據(broker、配置、主題、分區和副本)
KRaft
-
新架構中控制器節點形成了一個
Raft
仲裁,管理元數據事件日志,這個日志包含了集群元數據的每一個變更,原先保存在Zookeeper中的所有東西(主題、分區、ISR、配置等)都保存在這個日志中。 -
涉及直接與Zookeeper通信的客戶端和broker操作都通過控制器來路由,以達到無縫遷移。
-
使用
Raft
算法,控制節點可以在不依賴外部系統情況下選舉首領,首領節點被稱為主控制器,負責處理來自所有broker的RPC的調用,跟隨者控制器從主控制器復制數據,并會作為主控制器的熱備, -
其他broker通過API從主控制器獲取更新,而不是等待通知。broker將自己注冊到控制器仲裁上,在注銷前會一直保持注冊狀態。
復制
復制是Kafka架構核心的一部分,之所以這么重要,是因為他可以在個別節點失效時仍能保證Kafka的可用性和持久性。
Kafka中每個主題有若干分區,每個分區可以有多個副本,副本均勻的分布在多個broker中。
副本有兩種類型
- 首領副本:每個分區都有一個首領副本,為了保證一致性,所有生產者的請求都會經過這個副本。客戶端可以從首領副本或者跟隨者副本讀取數據
- 跟隨者副本:除了首領副本以外都是跟隨者副本。沒特別指定,跟隨者副本不處理來自客戶端的請求,主要任務是從首領副本復制消息,保持與首領一致的狀態。
請求的處理
客戶端持有集群的元數據緩存,元數據中包含了客戶端感興趣的主題清單以及主題包含的分區、副本、首領等,一般情況下客戶端會直接向目標broker發送生產請求和獲取請求。
請求分類
- 生產請求
- 獲取請求
- 管理請求
生產請求
生產者發送的請求,包含客戶端要寫入broker的消息
borker在接收到生產請求時會做一些驗證
- 發送數據的用戶是否有寫入權限
- 請求中
acks
參數是否有效 - 如果
acks=all
是否足夠多的同步副本保證消息已經寫入
消息寫入分區首領后,broker會檢查acks
參數,等到所有的都完成后,會返回響應給客戶端。
獲取請求
消費者和跟隨者副本發送的請求,用于從broker讀取消息。
broker接收到獲取請求時會做一些校驗
- 請求指定的偏移量是否存在
客戶端讀取消息時,Kafka使用零復制技術向客戶端發送消息。也就是說Kafka會直接把消息從文件里發送到網路通道,不經過任何緩沖區。
客戶端能讀取的消息是已經被寫入所有同步副本的消息;部分沒有完全同步給所有副本的消息是不會發送給消費者的。
管理請求
管理客戶端發送的請求,用于執行元數據操作,比如創建和刪除topic
存儲
分層存儲
- 本次存儲:與當前存儲一致,保存在broker機器上
- 優勢:響應快
- 劣勢:成本高、數據保留時間短
- 遠程存儲:利用HDFS、S3等存儲系統來存儲日志信息
- 優勢:成本低于本地存儲、數據可保留較長時間
- 劣勢:響應較慢
文件管理
數據保留是Kafka的一個重要概念
- Kafka中一個分區會分為若干片段,
- 默認每個片段包含1GB或者1周的數據,觸發任意上限,會關閉當前文件,重新打開一個文件
- 正在寫入的片段叫做活動片段,活動片段不會被刪除。
壓實
保留每個鍵的最新有效數據,同時清理歷史冗余的數據。
- 保留最新值:對于每條消息,如果指定了 Key,Kafka 會為每個 Key 保留最后一個寫入的 Value(最新狀態)。
- 刪除冗余記錄:所有舊版本的 Key-Value 對會被標記為可刪除(邏輯刪除),但物理刪除會在后臺異步完成。
- 非鍵消息的保留:沒有 Key 的消息(或 Key 為 null 的消息)不會被壓實,仍然遵循基于時間或大小的保留策略(例如 7 天后刪除)。
什么時候壓實主題
- 通過
log.cleaner.enabled
參數啟動壓實線程,線程會選擇渾濁率最高的分區來壓實。 - 默認情況下會在主題中有50%數據包含臟記錄時進行壓實。
- 每個日志片段分為兩個部分
- 干凈的部分:被壓實過的消息,每個鍵只有一個對應得值,是上一次壓實保留下來得
- 渾濁部分:上一次壓實之后寫入得