【深度知識】Kafka原理入門和詳解
- 2019 年 11 月 5 日
- 筆記
一、Kafka簡介
Kafka是由Apache軟件基金會開發的一個開源流處理平台,由Scala和Java編寫。Kafka是一種高吞吐量的分佈式發佈訂閱消息系統,它可以處理消費者在網站中的所有動作流數據。 這種動作(網頁瀏覽,搜索和其他用戶的行動)是在現代網絡上的許多社會功能的一個關鍵因素。 這些數據通常是由於吞吐量的要求而通過處理日誌和日誌聚合來解決。 對於像Hadoop一樣的日誌數據和離線分析系統,但又要求實時處理的限制,這是一個可行的解決方案。Kafka的目的是通過Hadoop的並行加載機制來統一線上和離線的消息處理,也是為了通過集群來提供實時的消息。
1.1 背景歷史
當今社會各種應用系統諸如商業、社交、搜索、瀏覽等像信息工廠一樣不斷的生產出各種信息,在大數據時代,我們面臨如下幾個挑戰:
- 如何收集這些巨大的信息
- 如何分析它
- 如何及時做到如上兩點
以上幾個挑戰形成了一個業務需求模型,即生產者生產(produce)各種信息,消費者消費(consume)(處理分析)這些信息,而在生產者與消費者之間,需要一個溝通兩者的橋樑-消息系統。從一個微觀層面來說,這種需求也可理解為不同的系統之間如何傳遞消息。
1.2 Kafka誕生
Kafka由 linked-in 開源 。 kafka-即是解決上述這類問題的一個框架,它實現了生產者和消費者之間的無縫連接。 kafka-高產出的分佈式消息系統(A high-throughput distributed messaging system)
1.3 Kafka現在
Apache kafka 是一個分佈式的基於push-subscribe的消息系統,它具備快速、可擴展、可持久化的特點。它現在是Apache旗下的一個開源系統,作為hadoop生態系統的一部分,被各種商業公司廣泛應用。它的最大的特性就是可以實時的處理大量數據以滿足各種需求場景:比如基於hadoop的批處理系統、低延遲的實時系統、storm/spark流式處理引擎。
二、Kafka技術概覽
2.1 Kafka的特性
- 高吞吐量、低延遲:kafka每秒可以處理幾十萬條消息,它的延遲最低只有幾毫秒
- 可擴展性:kafka集群支持熱擴展
- 持久性、可靠性:消息被持久化到本地磁盤,並且支持數據備份防止數據丟失
- 容錯性:允許集群中節點失敗(若副本數量為n,則允許n-1個節點失敗)
- 高並發:支持數千個客戶端同時讀寫
2.2 Kafka一些重要設計思想
下面介紹先大體介紹一下Kafka的主要設計思想,可以讓相關人員在短時間內了解到kafka相關特性,如果想深入研究,後面會對其中每一個特性都做詳細介紹。
- Consumergroup:各個consumer可以組成一個組,每個消息只能被組中的一個consumer消費,如果一個消息可以被多個consumer消費的話,那麼這些consumer必須在不同的組。
- 消息狀態:在Kafka中,消息的狀態被保存在consumer中,broker不會關心哪個消息被消費了被誰消費了,只記錄一個offset值(指向partition中下一個要被消費的消息位置),這就意味着如果consumer處理不好的話,broker上的一個消息可能會被消費多次。
- 消息持久化:Kafka中會把消息持久化到本地文件系統中,並且保持極高的效率。
- 消息有效期:Kafka會長久保留其中的消息,以便consumer可以多次消費,當然其中很多細節是可配置的。 批量發送:Kafka支持以消息集合為單位進行批量發送,以提高push效率。
- push-and-pull : Kafka中的Producer和consumer採用的是push-and-pull模式,即Producer只管向broker push消息,consumer只管從broker pull消息,兩者對消息的生產和消費是異步的。
- Kafka集群中broker之間的關係:不是主從關係,各個broker在集群中地位一樣,我們可以隨意的增加或刪除任何一個broker節點。
- 負載均衡方面: Kafka提供了一個 metadata API來管理broker之間的負載(對Kafka0.8.x而言,對於0.7.x主要靠zookeeper來實現負載均衡)。 同步異步:Producer採用異步push方式,極大提高Kafka系統的吞吐率(可以通過參數控制是採用同步還是異步方式)。
- 分區機制partition:Kafka的broker端支持消息分區,Producer可以決定把消息發到哪個分區,在一個分區中消息的順序就是Producer發送消息的順序,一個主題中可以有多個分區,具體分區的數量是可配置的。分區的意義很重大,後面的內容會逐漸體現。
- 離線數據裝載:Kafka由於對可拓展的數據持久化的支持,它也非常適合向Hadoop或者數據倉庫中進行數據裝載。
- 插件支持:現在不少活躍的社區已經開發出不少插件來拓展Kafka的功能,如用來配合Storm、Hadoop、flume相關的插件。
2.3 kafka 應用場景
- 日誌收集:一個公司可以用Kafka可以收集各種服務的log,通過kafka以統一接口服務的方式開放給各種consumer,例如hadoop、Hbase、Solr等。
- 消息系統:解耦和生產者和消費者、緩存消息等。
- 用戶活動跟蹤:Kafka經常被用來記錄web用戶或者app用戶的各種活動,如瀏覽網頁、搜索、點擊等活動,這些活動信息被各個服務器發佈到kafka的topic中,然後訂閱者通過訂閱這些topic來做實時的監控分析,或者裝載到hadoop、數據倉庫中做離線分析和挖掘。
- 運營指標:Kafka也經常用來記錄運營監控數據。包括收集各種分佈式應用的數據,生產各種操作的集中反饋,比如報警和報告。
- 流式處理:比如spark streaming和storm
- 事件源
2.4 Kafka架構組件
Kafka中發佈訂閱的對象是topic。我們可以為每類數據創建一個topic,把向topic發佈消息的客戶端稱作producer,從topic訂閱消息的客戶端稱作consumer。Producers和consumers可以同時從多個topic讀寫數據。一個kafka集群由一個或多個broker服務器組成,它負責持久化和備份具體的kafka消息。
- topic:消息存放的目錄即主題
- Producer:生產消息到topic的一方
- Consumer:訂閱topic消費消息的一方
- Broker:Kafka的服務實例就是一個broker

2.5 Kafka Topic&Partition
消息發送時都被發送到一個topic,其本質就是一個目錄,而topic由是由一些Partition Logs(分區日誌)組成,其組織結構如下圖所示:

我們可以看到,每個Partition中的消息都是有序的,生產的消息被不斷追加到Partition log上,其中的每一個消息都被賦予了一個唯一的offset值。 Kafka集群會保存所有的消息,不管消息有沒有被消費;我們可以設定消息的過期時間,只有過期的數據才會被自動清除以釋放磁盤空間。比如我們設置消息過期時間為2天,那麼這2天內的所有消息都會被保存到集群中,數據只有超過了兩天才會被清除。 Kafka需要維持的元數據只有一個–消費消息在Partition中的offset值,Consumer每消費一個消息,offset就會加1。其實消息的狀態完全是由Consumer控制的,Consumer可以跟蹤和重設這個offset值,這樣的話Consumer就可以讀取任意位置的消息。 把消息日誌以Partition的形式存放有多重考慮: 第一,方便在集群中擴展,每個Partition可以通過調整以適應它所在的機器,而一個topic又可以有多個Partition組成,因此整個集群就可以適應任意大小的數據了; 第二就是可以提高並發,因為可以以Partition為單位讀寫了。
三、Kafka 核心組件
3.1 Replications、Partitions 和Leaders
通過上面介紹的我們可以知道,kafka中的數據是持久化的並且能夠容錯的。Kafka允許用戶為每個topic設置副本數量,副本數量決定了有幾個broker來存放寫入的數據。如果你的副本數量設置為3,那麼一份數據就會被存放在3台不同的機器上,那麼就允許有2個機器失敗。一般推薦副本數量至少為2,這樣就可以保證增減、重啟機器時不會影響到數據消費。如果對數據持久化有更高的要求,可以把副本數量設置為3或者更多。 Kafka中的topic是以partition的形式存放的,每一個topic都可以設置它的partition數量,Partition的數量決定了組成topic的log的數量。Producer在生產數據時,會按照一定規則(這個規則是可以自定義的)把消息發佈到topic的各個partition中。上面將的副本都是以partition為單位的,不過只有一個partition的副本會被選舉成leader作為讀寫用。 關於如何設置partition值需要考慮的因素。一個partition只能被一個消費者消費(一個消費者可以同時消費多個partition),因此,如果設置的partition的數量小於consumer的數量,就會有消費者消費不到數據。所以,推薦partition的數量一定要大於同時運行的consumer的數量。另外一方面,建議partition的數量大於集群broker的數量,這樣leader partition就可以均勻的分佈在各個broker中,最終使得集群負載均衡。在Cloudera,每個topic都有上百個partition。需要注意的是,kafka需要為每個partition分配一些內存來緩存消息數據,如果partition數量越大,就要為kafka分配更大的heap space。
3.2 Producers
Producers直接發送消息到broker上的leader partition,不需要經過任何中介一系列的路由轉發。為了實現這個特性,kafka集群中的每個broker都可以響應producer的請求,並返回topic的一些元信息,這些元信息包括哪些機器是存活的,topic的leader partition都在哪,現階段哪些leader partition是可以直接被訪問的。 Producer客戶端自己控制着消息被推送到哪些partition。實現的方式可以是隨機分配、實現一類隨機負載均衡算法,或者指定一些分區算法。Kafka提供了接口供用戶實現自定義的分區,用戶可以為每個消息指定一個partitionKey,通過這個key來實現一些hash分區算法。比如,把userid作為partitionkey的話,相同userid的消息將會被推送到同一個分區。 以Batch的方式推送數據可以極大的提高處理效率,kafka Producer 可以將消息在內存中累計到一定數量後作為一個batch發送請求。Batch的數量大小可以通過Producer的參數控制,參數值可以設置為累計的消息的數量(如500條)、累計的時間間隔(如100ms)或者累計的數據大小(64KB)。通過增加batch的大小,可以減少網絡請求和磁盤IO的次數,當然具體參數設置需要在效率和時效性方面做一個權衡。 Producers可以異步的並行的向kafka發送消息,但是通常producer在發送完消息之後會得到一個future響應,返回的是offset值或者發送過程中遇到的錯誤。這其中有個非常重要的參數「acks」,這個參數決定了producer要求leader partition 收到確認的副本個數,如果acks設置數量為0,表示producer不會等待broker的響應,所以,producer無法知道消息是否發送成功,這樣有可能會導致數據丟失,但同時,acks值為0會得到最大的系統吞吐量。 若acks設置為1,表示producer會在leader partition收到消息時得到broker的一個確認,這樣會有更好的可靠性,因為客戶端會等待直到broker確認收到消息。若設置為-1,producer會在所有備份的partition收到消息時得到broker的確認,這個設置可以得到最高的可靠性保證。 Kafka 消息有一個定長的header和變長的位元組數組組成。因為kafka消息支持位元組數組,也就使得kafka可以支持任何用戶自定義的序列號格式或者其它已有的格式如Apache Avro、protobuf等。Kafka沒有限定單個消息的大小,但我們推薦消息大小不要超過1MB,通常一般消息大小都在1~10kB之前。
3.3 Consumers
Kafka提供了兩套consumer api,分為high-level api和sample-api。Sample-api 是一個底層的API,它維持了一個和單一broker的連接,並且這個API是完全無狀態的,每次請求都需要指定offset值,因此,這套API也是最靈活的。 在kafka中,當前讀到消息的offset值是由consumer來維護的,因此,consumer可以自己決定如何讀取kafka中的數據。比如,consumer可以通過重設offset值來重新消費已消費過的數據。不管有沒有被消費,kafka會保存數據一段時間,這個時間周期是可配置的,只有到了過期時間,kafka才會刪除這些數據。 High-level API封裝了對集群中一系列broker的訪問,可以透明的消費一個topic。它自己維持了已消費消息的狀態,即每次消費的都是下一個消息。 High-level API還支持以組的形式消費topic,如果consumers有同一個組名,那麼kafka就相當於一個隊列消息服務,而各個consumer均衡的消費相應partition中的數據。若consumers有不同的組名,那麼此時kafka就相當與一個廣播服務,會把topic中的所有消息廣播到每個consumer。

四、Kafka核心特性
4.1 壓縮
我們上面已經知道了Kafka支持以集合(batch)為單位發送消息,在此基礎上,Kafka還支持對消息集合進行壓縮,Producer端可以通過GZIP或Snappy格式對消息集合進行壓縮。Producer端進行壓縮之後,在Consumer端需進行解壓。壓縮的好處就是減少傳輸的數據量,減輕對網絡傳輸的壓力,在對大數據處理上,瓶頸往往體現在網絡上而不是CPU(壓縮和解壓會耗掉部分CPU資源)。 那麼如何區分消息是壓縮的還是未壓縮的呢,Kafka在消息頭部添加了一個描述壓縮屬性位元組,這個位元組的後兩位表示消息的壓縮採用的編碼,如果後兩位為0,則表示消息未被壓縮。
4.2消息可靠性
在消息系統中,保證消息在生產和消費過程中的可靠性是十分重要的,在實際消息傳遞過程中,可能會出現如下三中情況:
一個消息發送失敗 一個消息被發送多次 最理想的情況:exactly-once ,一個消息發送成功且僅發送了一次
有許多系統聲稱它們實現了exactly-once,但是它們其實忽略了生產者或消費者在生產和消費過程中有可能失敗的情況。比如雖然一個Producer成功發送一個消息,但是消息在發送途中丟失,或者成功發送到broker,也被consumer成功取走,但是這個consumer在處理取過來的消息時失敗了。 從Producer端看:Kafka是這麼處理的,當一個消息被發送後,Producer會等待broker成功接收到消息的反饋(可通過參數控制等待時間),如果消息在途中丟失或是其中一個broker掛掉,Producer會重新發送(我們知道Kafka有備份機制,可以通過參數控制是否等待所有備份節點都收到消息)。 從Consumer端看:前面講到過partition,broker端記錄了partition中的一個offset值,這個值指向Consumer下一個即將消費message。當Consumer收到了消息,但卻在處理過程中掛掉,此時Consumer可以通過這個offset值重新找到上一個消息再進行處理。Consumer還有權限控制這個offset值,對持久化到broker端的消息做任意處理。
4.3 備份機制
備份機制是Kafka0.8版本的新特性,備份機制的出現大大提高了Kafka集群的可靠性、穩定性。有了備份機制後,Kafka允許集群中的節點掛掉後而不影響整個集群工作。一個備份數量為n的集群允許n-1個節點失敗。在所有備份節點中,有一個節點作為lead節點,這個節點保存了其它備份節點列表,並維持各個備份間的狀體同步。下面這幅圖解釋了Kafka的備份機制:

4.4 Kafka高效性相關設計
4.4.1 消息的持久化
Kafka高度依賴文件系統來存儲和緩存消息,一般的人認為磁盤是緩慢的,這導致人們對持久化結構具有競爭性持懷疑態度。其實,磁盤遠比你想像的要快或者慢,這決定於我們如何使用磁盤。 一個和磁盤性能有關的關鍵事實是:磁盤驅動器的吞吐量跟尋到延遲是相背離的,也就是所,線性寫的速度遠遠大於隨機寫。比如:在一個6 7200rpm SATA RAID-5 的磁盤陣列上線性寫的速度大概是600M/秒,但是隨機寫的速度只有100K/秒,兩者相差將近6000倍。線性讀寫在大多數應用場景下是可以預測的,因此,操作系統利用read-ahead和write-behind技術來從大的數據塊中預取數據,或者將多個邏輯上的寫操作組合成一個大寫物理寫操作中。更多的討論可以在ACMQueueArtical中找到,他們發現,對磁盤的線性讀在有些情況下可以比內存的隨機訪問要快一些。 為了補償這個性能上的分歧,現代操作系統都會把空閑的內存用作磁盤緩存,儘管在內存回收的時候會有一點性能上的代價。所有的磁盤讀寫操作會在這個統一的緩存上進行。 此外,如果我們是在JVM的基礎上構建的,熟悉java內存應用管理的人應該清楚以下兩件事情:
- 一個對象的內存消耗是非常高的,經常是所存數據的兩倍或者更多。
- 隨着堆內數據的增多,Java的垃圾回收會變得非常昂貴。
基於這些事實,利用文件系統並且依靠頁緩存比維護一個內存緩存或者其他結構要好——我們至少要使得可用的緩存加倍,通過自動訪問可用內存,並且通過存儲更緊湊的位元組結構而不是一個對象,這將有可能再次加倍。這麼做的結果就是在一台32GB的機器上,如果不考慮GC懲罰,將最多有28-30GB的緩存。此外,這些緩存將會一直存在即使服務重啟,然而進程內緩存需要在內存中重構(10GB緩存需要花費10分鐘)或者它需要一個完全冷緩存啟動(非常差的初始化性能)。它同時也簡化了代碼,因為現在所有的維護緩存和文件系統之間內聚的邏輯都在操作系統內部了,這使得這樣做比one-off in-process attempts更加高效與準確。如果你的磁盤應用更加傾向於順序讀取,那麼read-ahead在每次磁盤讀取中實際上獲取到這人緩存中的有用數據。 以上這些建議了一個簡單的設計:不同於維護儘可能多的內存緩存並且在需要的時候刷新到文件系統中,我們換一種思路。所有的數據不需要調用刷新程序,而是立刻將它寫到一個持久化的日誌中。事實上,這僅僅意味着,數據將被傳輸到內核頁緩存中並稍後被刷新。我們可以增加一個配置項以讓系統的用戶來控制數據在什麼時候被刷新到物理硬盤上。
4.4.2 常數時間性能保證
消息系統中持久化數據結構的設計通常是維護者一個和消費隊列有關的B樹或者其它能夠隨機存取結構的元數據信息。B樹是一個很好的結構,可以用在事務型與非事務型的語義中。但是它需要一個很高的花費,儘管B樹的操作需要O(logN)。通常情況下,這被認為與常數時間等價,但這對磁盤操作來說是不對的。磁盤尋道一次需要10ms,並且一次只能尋一個,因此並行化是受限的。 直覺上來講,一個持久化的隊列可以構建在對一個文件的讀和追加上,就像一般情況下的日誌解決方案。儘管和B樹相比,這種結構不能支持豐富的語義,但是它有一個優點,所有的操作都是常數時間,並且讀寫之間不會相互阻塞。這種設計具有極大的性能優勢:最終系統性能和數據大小完全無關,服務器可以充分利用廉價的硬盤來提供高效的消息服務。 事實上還有一點,磁盤空間的無限增大而不影響性能這點,意味着我們可以提供一般消息系統無法提供的特性。比如說,消息被消費後不是立馬被刪除,我們可以將這些消息保留一段相對比較長的時間(比如一個星期)。
4.4.3 進一步提高效率
我們已經為效率做了非常多的努力。但是有一種非常主要的應用場景是:處理Web活動數據,它的特點是數據量非常大,每一次的網頁瀏覽都會產生大量的寫操作。更進一步,我們假設每一個被發佈的消息都會被至少一個consumer消費,因此我們更要怒路讓消費變得更廉價。 通過上面的介紹,我們已經解決了磁盤方面的效率問題,除此之外,在此類系統中還有兩類比較低效的場景:
- 太多小的I/O操作
- 過多的位元組拷貝
為了減少大量小I/O操作的問題,kafka的協議是圍繞消息集合構建的。Producer一次網絡請求可以發送一個消息集合,而不是每一次只發一條消息。在server端是以消息塊的形式追加消息到log中的,consumer在查詢的時候也是一次查詢大量的線性數據塊。消息集合即MessageSet,實現本身是一個非常簡單的API,它將一個位元組數組或者文件進行打包。所以對消息的處理,這裡沒有分開的序列化和反序列化的上步驟,消息的字段可以按需反序列化(如果沒有需要,可以不用反序列化)。 另一個影響效率的問題就是位元組拷貝。為了解決位元組拷貝的問題,kafka設計了一種「標準位元組消息」,Producer、Broker、Consumer共享這一種消息格式。Kakfa的message log在broker端就是一些目錄文件,這些日誌文件都是MessageSet按照這種「標準位元組消息」格式寫入到磁盤的。 維持這種通用的格式對這些操作的優化尤為重要:持久化log 塊的網絡傳輸。流行的unix操作系統提供了一種非常高效的途徑來實現頁面緩存和socket之間的數據傳遞。在Linux操作系統中,這種方式被稱作:sendfile system call(Java提供了訪問這個系統調用的方法:FileChannel.transferTo api)。
為了理解sendfile的影響,需要理解一般的將數據從文件傳到socket的路徑:
操作系統將數據從磁盤讀到內核空間的頁緩存中 應用將數據從內核空間讀到用戶空間的緩存中 應用將數據寫回內核空間的socket緩存中 操作系統將數據從socket緩存寫到網卡緩存中,以便將數據經網絡發出 這種操作方式明顯是非常低效的,這裡有四次拷貝,兩次系統調用。如果使用sendfile,就可以避免兩次拷貝:操作系統將數據直接從頁緩存發送到網絡上。所以在這個優化的路徑中,只有最後一步將數據拷貝到網卡緩存中是需要的。 我們期望一個主題上有多個消費者是一種常見的應用場景。利用上述的zero-copy,數據只被拷貝到頁緩存一次,然後就可以在每次消費時被重得利用,而不需要將數據存在內存中,然後在每次讀的時候拷貝到內核空間中。這使得消息消費速度可以達到網絡連接的速度。這樣以來,通過頁面緩存和sendfile的結合使用,整個kafka集群幾乎都已以緩存的方式提供服務,而且即使下游的consumer很多,也不會對整個集群服務造成壓力。 關於sendfile和zero-copy,請參考:zero-copy
五、Kafka集群部署
5.1 集群部署
為了提高性能,推薦採用專用的服務器來部署kafka集群,盡量與hadoop集群分開,因為kafka依賴磁盤讀寫和大的頁面緩存,如果和hadoop共享節點的話會影響其使用頁面緩存的性能。 Kafka集群的大小需要根據硬件的配置、生產者消費者的並發數量、數據的副本個數、數據的保存時長綜合確定。 磁盤的吞吐量尤為重要,因為通常kafka的瓶頸就在磁盤上。 Kafka依賴於zookeeper,建議採用專用服務器來部署zookeeper集群,zookeeper集群的節點採用偶數個,一般建議用3、5、7個。注意zookeeper集群越大其讀寫性能越慢,因為zookeeper需要在節點之間同步數據。一個3節點的zookeeper集群允許一個節點失敗,一個5節點集群允許2個幾點失敗。
5.2 集群大小
有很多因素決定着kafka集群需要具備存儲能力的大小,最準確的衡量辦法就是模擬負載來測算一下,Kafka本身也提供了負載測試的工具。 如果不想通過模擬實驗來評估集群大小,最好的辦法就是根據硬盤的空間需求來推算。下面我就根據網絡和磁盤吞吐量需求來做一下估算。 我們做如下假設: W:每秒寫多少MB R :副本數 C :Consumer的數量
一般的來說,kafka集群瓶頸在於網絡和磁盤吞吐量,所以我們先評估一下集群的網絡和磁盤需求。 對於每條消息,每個副本都要寫一遍,所以整體寫的速度是WR。讀數據的部分主要是集群內部各個副本從leader同步消息讀和集群外部的consumer讀,所以集群內部讀的速率是(R-1)W,同時,外部consumer讀的速度是CW,因此: Write:WR Read:(R-1)W+CW
需要注意的是,我們可以在讀的時候緩存部分數據來減少IO操作,如果一個集群有M MB內存,寫的速度是W MB/sec,則允許M/(W*R) 秒的寫可以被緩存。如果集群有32GB內存,寫的速度是50MB/s的話,則可以至少緩存10分鐘的數據。
5.3 Kafka性能測試
5.4 Kafka在zookeeper中的數據結構
Kafka data structures in Zookeeper
六、Kafka主要配置
其他文章介紹。
七、kafka消費模型
一般有兩種消費模型,不同模型下消費者的行為是不同的:
- 隊列模式(也叫點對點模式)。多個消費者共同消費一個隊列,每條消息只發送給一個消費者。
- 發佈/訂閱模式。多個消費者訂閱主題,每個消息會發佈給所有的消費者。

兩種方式各有優缺點:
- 隊列模式中多個消費者共同消費同一個隊列,效率高。
- 發佈/訂閱模式中,一個消息可以被多次消費,能支持冗餘的消費(例如兩個消費者共同消費一個消息,防止其中某個消費者掛了)
顯然要構建一個大數據下的消息隊列,兩種模式都是必須的。因此 Kafka 引入了 Consumer Group(消費組)的概念,Consumer Group 是以發佈/訂閱模式工作的;一個 Consumer Group 中可以有多個 Consumer(消費者),Group 內的消費者以隊列模式工作,如下圖:

上面提到,Kafka 中的消息是以 Partition 存儲的。