Kafka 架構及基本原理簡析

作者:老羊_肖恩
鏈接:https://www.jianshu.com/p/6237e5d59afd

Kafka 簡介

Kafka 是一個由 Scala 和 Java 編寫的企業級的消息發佈和訂閱系統,最早是由 Linkedin 公司開發,最終開源到 Apache 軟件基金會的項目。Kafka 是一個分佈式的,支持分區的,多副本的和多訂閱者的高吞吐量的消息系統,被廣泛應用在應用解耦、異步處理、限流削峯和消息驅動等場景。本文將針對 Kafka 的架構和相關組件進行簡單的介紹。在介紹 Kafka 的架構之前,我們先了解一下 Kafk 的核心概念。

Kafka 核心概念

在詳細介紹 Kafka 的架構和基本組件之前,需要先了解一下 Kafka 的一些核心概念。
Producer: 消息的生產者,負責往 Kafka 集羣中發送消息;
Consumer: 消息的消費者,主動從 Kafka 集羣中拉取消息。
Consumer Group: 每個 Consumer 屬於一個特定的 Consumer Group,新建 Consumer 的時候需要指定對應的 Consumer Group ID。
Broker: Kafka 集羣中的服務實例,也稱之爲節點,每個 Kafka 集羣包含一個或者多個 Broker(一個 Broker 就是一個服務器或節點)。
Message: 通過 Kafka 集羣進行傳遞的對象實體,存儲需要傳送的信息。
Topic: 消息的類別,主要用於對消息進行邏輯上的區分,每條發送到 Kafka 集羣的消息都需要有一個指定的 Topic,消費者根據 Topic 對指定的消息進行消費。
Partition: 消息的分區,Partition 是一個物理上的概念,相當於一個文件夾,Kafka 會爲每個 topic 的每個分區創建一個文件夾,一個 Topic 的消息會存儲在一個或者多個 Partition 中。
Segment: 一個 partition 當中存在多個 segment 文件段(分段存儲),每個 Segment 分爲兩部分,.log 文件和 .index 文件,其中 .index 文件是索引文件,主要用於快速查詢. log 文件當中數據的偏移量位置;
.log 文件: 存放 Message 的數據文件,在 Kafka 中把數據文件就叫做日誌文件。一個分區下面默認有 n 多個. log 文件(分段存儲)。一個. log 文件大默認 1G,消息會不斷追加在. log 文件中,當. log 文件的大小超過 1G 的時候,會自動新建一個新的. log 文件。
.index 文件: 存放. log 文件的索引數據,每個. index 文件有一個對應同名的. log 文件。

後面我們會對上面的一些核心概念進行更深入的介紹。在介紹完 Kafka 的核心概念之後,我們來看一下 Kafka 的對外提供的基本功能,組件及架構設計。

Kafka API

如上圖所示,Kafka 主要包含四個主要的 API 組件:
1. Producer API
應用程序通過 Producer API 向 Kafka 集羣發送一個或多個 Topic 的消息。

2. Consumer API
應用程序通過 Consumer API,向 Kafka 集羣訂閱一個或多個 Topic 的消息,並處理這些 Topic 下接收到的消息。

3. Streams API
應用程序通過使用 Streams API 充當流處理器(Stream Processor),從一個或者多個 Topic 獲取輸入流,並生產一個輸出流到一個或者多個 Topic,能夠有效地將輸入流進行轉變後變成輸出流輸出到 Kafka 集羣。

4. Connect API
允許應用程序通過 Connect API 構建和運行可重用的生產者或者消費者,能夠把 kafka 主題連接到現有的應用程序或數據系統。Connect 實際上就做了兩件事情:使用 Source Connector 從數據源(如:DB)中讀取數據寫入到 Topic 中,然後再通過 Sink Connector 讀取 Topic 中的數據輸出到另一端(如:DB),以實現消息數據在外部存儲和 Kafka 集羣之間的傳輸。

Kafka 架構

接下來我們將從 Kafka 的架構出發,重點介紹 Kafka 的主要組件及實現原理。Kafka 支持消息持久化,消費端是通過主動拉取消息進行消息消費的,訂閱狀態和訂閱關係由客戶端負責維護,消息消費完後不會立刻刪除,會保留歷史消息,一般默認保留 7 天,因此可以通過在支持多訂閱者時,消息無需複製多分,只需要存儲一份就可以。下面將詳細介紹每個組件的實現原理。
1. Producer
Producer 是 Kafka 中的消息生產者,主要用於生產帶有特定 Topic 的消息,生產者生產的消息通過 Topic 進行歸類,保存在 Kafka 集羣的 Broker 上,具體的是保存在指定的 partition 的目錄下,以 Segment 的方式(.log 文件和. index 文件)進行存儲。

2. Consumer
Consumer 是 Kafka 中的消費者,主要用於消費指定 Topic 的消息,Consumer 是通過主動拉取的方式從 Kafka 集羣中消費消息,消費者一定屬於某一個特定的消費組。

3. Topic
Kafka 中的消息是根據 Topic 進行分類的,Topic 是支持多訂閱的,一個 Topic 可以有多個不同的訂閱消息的消費者。Kafka 集羣 Topic 的數量沒有限制,同一個 Topic 的數據會被劃分在同一個目錄下,一個 Topic 可以包含 1 至多個分區,所有分區的消息加在一起就是一個 Topic 的所有消息。

4. Partition
在 Kafka 中,爲了提升消息的消費速度,可以爲每個 Topic 分配多個 Partition,這也是就之前我們說到的,Kafka 是支持多分區的。默認情況下,一個 Topic 的消息只存放在一個分區中。Topic 的所有分區的消息合併起來,就是一個 Topic 下的所有消息。每個分區都有一個從 0 開始的編號,每個分區內的數據都是有序的,但是不同分區直接的數據是不能保證有序的,因爲不同的分區需要不同的 Consumer 去消費,每個 Partition 只能分配一個 Consumer,但是一個 Consumer 可以同時一個 Topic 的多個 Partition。

5. Consumer Group
Kafka 中的每一個 Consumer 都歸屬於一個特定的 Consumer Group,如果不指定,那麼所有的 Consumer 都屬於同一個默認的 Consumer Group。Consumer Group 由一個或多個 Consumer 組成,同一個 Consumer Group 中的 Consumer 對同一條消息只消費一次。每個 Consumer Group 都有一個唯一的 ID,即 Group ID,也稱之爲 Group Name。Consumer Group 內的所有 Consumer 協調在一起訂閱一個 Topic 的所有 Partition,且每個 Partition 只能由一個 Consuemr Group 中的一個 Consumer 進行消費,但是可以由不同的 Consumer Group 中的一個 Consumer 進行消費。如下圖所示:

在層級關係上來說 Consumer 好比是跟 Topic 對應的,而 Consumer 就對應於 Topic 下的 Partition。Consumer Group 中的 Consumer 數量和 Topic 下的 Partition 數量共同決定了消息消費的併發量,且 Partition 數量決定了最終併發量,因爲一個 Partition 只能由一個 Consumer 進行消費。當一個 Consumer Group 中 Consumer 數量超過訂閱的 Topic 下的 Partition 數量時,Kafka 會爲每個 Partition 分配一個 Consumer,多出來的 Consumer 會處於空閒狀態。當 Consumer Group 中 Consumer 數量少於當前定於的 Topic 中的 Partition 數量是,單個 Consumer 將承擔多個 Partition 的消費工作。如上圖所示,Consumer Group B 中的每個 Consumer 需要消費兩個 Partition 中的數據,而 Consumer Group C 中會多出來一個空閒的 Consumer4。總結下來就是:同一個 Topic 下的 Partition 數量越多,同一時間可以有越多的 Consumer 進行消費,消費的速度就會越快,吞吐量就越高。同時,Consumer Group 中的 Consumer 數量需要控制爲小於等於 Partition 數量,且最好是整數倍:如 1,2,4 等。

6. Segment
考慮到消息消費的性能,Kafka 中的消息在每個 Partition 中是以分段的形式進行存儲的,即每 1G 消息新建一個 Segment,每個 Segment 包含兩個文件:.log 文件和. index 文件。之前我們已經說過,.log 文件就是 Kafka 實際存儲 Producer 生產的消息,而. index 文件採用稀疏索引的方式存儲. log 文件中對應消息的邏輯編號和物理偏移地址(offset),以便於加快數據的查詢速度。.log 文件和. index 文件是一一對應,成對出現的。下圖展示了. log 文件和. index 文件在 Partition 中的存在方式。

Kafka 裏面每一條消息都有自己的邏輯 offset(相對偏移量)以及存在物理磁盤上面實際的物理地址便宜量 Position,也就是說在 Kafka 中一條消息有兩個位置:offset(相對偏移量)和 position(磁盤物理偏移地址)。在 kafka 的設計中,將消息的 offset 作爲了 Segment 文件名的一部分。Segment 文件命名規則爲:Partition 全局的第一個 Segment 從 0 開始,後續每個 segment 文件名爲上一個 Partition 的最大 offset(Message 的 offset,非實際物理地偏移地址,實際物理地址需映射到. log 中,後面會詳細介紹在. log 文件中查詢消息的原理)。數值最大爲 64 位 long 大小,由 20 位數字表示,前置用 0 填充。

上圖展示了. index 文件和. log 文件直接的映射關係,通過上圖,我們可以簡單介紹一下 Kafka 在 Segment 中查找 Message 的過程:
  1. 根據需要消費的下一個消息的 offset,這裏假設是 7,使用二分查找在 Partition 中查找到文件名小於(一定要小於,因爲文件名編號等於當前 offset 的文件裏存的都是大於當前 offset 的消息)當前 offset 的最大編號的. index 文件,這裏自然是查找到了 00000000000000000000.index。
  2. 在. index 文件中,使用二分查找,找到 offset 小於或者等於指定 offset(這裏假設是 7)的最大的 offset,這裏查到的是 6,然後獲取到 index 文件中 offset 爲 6 指向的 Position(物理偏移地址)爲 258。
  3. 在. log 文件中,從磁盤位置 258 開始順序掃描,直到找到 offset 爲 7 的 Message。
至此,我們就簡單介紹完了 Segment 的基本組件. index 文件和. log 文件的存儲和查詢原理。但是我們會發現一個問題:.index 文件中的 offset 並不是按順序連續存儲的,爲什麼 Kafka 要將索引文件設計成這種不連續的樣子?這種不連續的索引設計方式稱之爲稀疏索引,Kafka 中採用了稀疏索引的方式讀取索引,kafka 每當. log 中寫入了 4k 大小的數據,就往. index 裏以追加的寫入一條索引記錄。使用稀疏索引主要有以下原因:
  (1) 索引稀疏存儲,可以大幅降低. index 文件佔用存儲空間大小。
  (2) 稀疏索引文件較小,可以全部讀取到內存中,可以避免讀取索引的時候進行頻繁的 IO 磁盤操作,以便通過索引快速地定位到. log 文件中的 Message。

7. Message
Message 是實際發送和訂閱的信息是實際載體,Producer 發送到 Kafka 集羣中的每條消息,都被 Kafka 包裝成了一個 Message 對象,之後再存儲在磁盤中,而不是直接存儲的。Message 在磁盤中的物理結構如下所示。

On-disk format of a message

offset         : 8 bytes 
message length : 4 bytes (value: 4 + 1 + 1 + 8(if magic value > 0) + 4 + K + 4 + V)
crc            : 4 bytes
magic value    : 1 byte
attributes     : 1 byte
timestamp      : 8 bytes (Only exists when magic value is greater than zero)
key length     : 4 bytes
key            : K bytes
value length   : 4 bytes
value          : V bytes

其中keyvalue存儲的是實際的 Message 內容,長度不固定,而其他都是對 Message 內容的統計和描述,長度固定。因此在查找實際 Message 過程中,磁盤指針會根據 Message 的offsetmessage length計算移動位數,以加速 Message 的查找過程。之所以可以這樣加速,因爲 Kafka 的. log 文件都是順序寫的,往磁盤上寫數據時,就是追加數據,沒有隨機寫的操作。

8.Partition Replicas
最後我們簡單聊一下 Kafka 中的 Partition Replicas(分區副本)機制,0.8 版本以前的 Kafka 是沒有副本機制的。創建 Topic 時,可以爲 Topic 指定分區,也可以指定副本個數。kafka 中的分區副本如下圖所示:

Kafka 通過副本因子(replication-factor)控制消息副本保存在幾個 Broker(服務器)上,一般情況下副本數等於 Broker 的個數,且同一個副本因子不能放在同一個 Broker 中。副本因子是以分區爲單位且區分角色;主副本稱之爲 Leader(任何時刻只有一個),從副本稱之爲 Follower(可以有多個),處於同步狀態的副本叫做 in-sync-replicas(ISR)。Leader 負責讀寫數據,Follower 不負責對外提供數據讀寫,只從 Leader 同步數據,消費者和生產者都是從 leader 讀寫數據,不與 follower 交互,因此 Kafka 並不是讀寫分離的。同時使用 Leader 進行讀寫的好處是,降低了數據同步帶來的數據讀取延遲,因爲 Follower 只能從 Leader 同步完數據之後才能對外提供讀取服務。

如果一個分區有三個副本因子,就算其中一個掛掉,那麼只會剩下的兩個中,選擇一個 leader,如下圖所示。但不會在其他的 broker 中,另啓動一個副本(因爲在另一臺啓動的話,必然存在數據拷貝和傳輸,會長時間佔用網絡 IO,Kafka 是一個高吞吐量的消息系統,這個情況不允許發生)。如果指定分區的所有副本都掛了,Consumer 如果發送數據到指定分區的話,將寫入不成功。Consumer 發送到指定 Partition 的消息,會首先寫入到 Leader Partition 中,寫完後還需要把消息寫入到 ISR 列表裏面的其它分區副本中,寫完之後這個消息才能提交 offset。

到這裏,差不多把 Kafka 的架構和基本原理簡單介紹完了。Kafka 爲了實現高吞吐量和容錯,還引入了很多優秀的設計思路,如零拷貝,高併發網絡設計,順序存儲,以後有時間再說。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/7ZT31OhDjn0kOkYZS_z0oA