Kafka 是如何建模數據的?

Kafka 是一個高度可擴展的消息系統,具有優秀的水平可擴展性和高吞吐率的特點,因而被許多公司所青睞並得到廣泛的使用。本文首先介紹 Kafka 誕生的時代背景以及誕生之初的設計目標,隨後回答 Kafka 作爲一個消息系統是如何建模數據的,最後講解 Kafka 作爲一個軟件系統的架構,爲有志於深入瞭解的 Kafka 的同學做一個簡單的框架梳理。

Kafka 誕生的時代背景與設計目標

大數據時代,爲了分析數據背後的價值,組織所有服務和系統所產生的數據在權限允許的範圍內對所有服務和系統都應該是可用的。從原始數據的收集到數據的使用,自底向上是一個金字塔形的結構,即底部以某種統一的方式採集數據,以統一的方式對數據建模,通過統一的方式對上層服務和應用提供訪問和處理接口。

現實世界裏,企業的每個應用程序都會產生數據,包括日誌信息、監控指標、用戶行爲和請求響應等等。這些數據的產生源頭不盡相同,可能散落在不同業務團隊的邏輯代碼當中。在傳統的軟件開發世界裏,這些數據的持久化位置也不盡相同,日誌文件可能存儲在服務運行的機器的文件系統上,監控指標可能在監控系統擁有的數據庫裏,用戶行爲可能落到了數據倉庫中,請求響應甚至沒有被合理的持久化。

我們再從數據消費一端來觀察這個過程,現在一個商業智能分析系統想要從不同業務的監控指標裏聚合出統一的指標頁面以供運營方快速決策應該採取什麼行動來提升公司的盈利。最糟糕的情況莫過於該系統發現不同業務擁有不同的存放指標的方式,因此你要爲接入每個系統的指標上報實現完全不同的邏輯,每來一個新的業務,就需要對接一個新的指標上報流程。對於一個新的分析系統來說,在它冷啓動的時候,就要對接所有產生數據的業務方。這將成爲所有開發人員的噩夢。

軟件開發的一個重要技能就是觀察出系統中存在的重複邏輯並抽出通用的模塊以提升複用率和開發效率。注意到上面的數據對接流程涉及到數據的生產方和消費方,每對生產消費組合都要確定一個數據對接的方案。但是,數據的對接之所以會要求不同的方案,是因爲數據的存儲和模式是異構的。自然而然的,我們會想到定義一種統一的、足夠通用以支持覆蓋各種場景的數據交互模式,在數據的生產方和消費方之間引入一箇中間層來解耦相互瞭解的負擔。可以看到,原先有 N 個生產方和 M 個消費方,對接的要求次數是 N x M 次,而引入一個統一的數據交互中間層,消費方和生產方只需要分別對接這一中間層,對接的要求是 N + M 次。數據協同的複雜度被大大降低了。

這樣的中間層解耦思路就是所謂的發佈訂閱系統。不同於數據的消費方與生產方直接建立聯繫,在系統裏引入一個統一的消息總線以支持消息的發佈與訂閱。因爲一方與另一方的交互被這一總線所隱藏,因此原來 N 的對接需求也就變成了 1 的對接需求。

然而,在上面的場景裏,只有商業智能分析這一個類型的不同需求方。在現實世界當中,我們還會遇到日誌搜索的數據需求方,風險控制的數據需求方等等。爲每個數據處理領域維護一個領域專門的,但實現又高度相似的發佈訂閱系統是不合實際的,這會導致各個大同小異的系統有着各自的缺陷和不足。在業務團隊對接不同的數據處理領域時,仍然需要進行相似但又細微不同的數據上報邏輯,這些細微的不同將成爲後期維護深海暗礁。

爲了從公司層面解耦數據交互,實際上需要的是一個同構的消息系統,它可以用來發布通用類型的數據,其規模可以隨着公司業務的增長平穩的水平擴展。

這個同構的消息系統方案在 LinkedIn 的實現就是 Kafka,它能夠作爲數據源或數據匯,並且數據生產者上報或消費者訪問的繼承工作只需要連接到 Kafka 的一個單獨的管道,而無須連接到實際的對應的消費者或生產者。Kafka 作爲消息總線,管理從各個應用程序彙集到此的消息流,經過處理以後在分發到所有對數據感興趣的訂閱方。

Kafka 作爲一種分佈式的、基於發佈訂閱模式的消息系統,它的主要涉及目標包括以下幾點。

  1. 首先,解耦數據的生產者和消費者。在項目研發的過程過程中,數據的產生和處理會根據需求發生什麼樣的變化是不可預知的。Kafka 作爲消息交互的中間層,提供統一的消息交互模式。只要生產者按照統一的模式上報,消費者就可以按照相同的模式來訪問和處理。這使得開發者能夠獨立的修改數據的產生或處理的邏輯。
  2. 數據的消費在整個系統中並不是一次性的,既不只給一個消費者消費,也不只給消費者消費一次,而是可以支持多個消費者消費若干次。Kafka 按照一定順序持久化保存數據,從而支持通過重放消息來重複消費同一份數據;同時,通過消費組來區分不同的消費者羣體,以支持同一份數據被不同的消費者所消費。
  3. Kafka 作爲一個分佈式消息系統,需要能夠容忍分佈式系統固有的任意故障,同時要支持在數據增長或減少的時候通過水平伸縮平滑地應對變化。

Kafka 建模數據的方式

消息

Kafka 的數據單元就叫做消息,消息的概念可以和數據庫裏的一行數據或者一條記錄相對應。Kafka 看到的消息是字節數組形式的,因此對 Kafka 來說消息裏的數據沒有特別的模式或含義。消息可以帶有一個鍵( Key ),鍵在 Kafka 中也是字節數組形式,因此沒有特別的模式或含義,只是作爲後續將提到的消息分區的邏輯中的一個參考數據。

主題和分區

Kafka 的消息通過主題( topic )來進行分類,主題的概念可以類比數據庫裏的表,可以將同一個主題下的消息看做帶有主題標籤的消息,Kafka 消費消息的粒度就是按照主題來選擇的。主題可以被分爲若干個分區( partition ),分區是性能提升的核心概念。對主題做分區的本質是將主題的數據切分到多個並行的數據流中,這也是 Kafka 能夠實現巨大吞吐量的關鍵。同一個分區的消息以追加的形式寫入,然後按照先進先出的順序被讀取。不同分區之間的消息無法排序,但同一個分區的消息將按照進入 Kafka 系統的時間順序消費。注意這個時間戳未必是消息攜帶的業務時間戳。通過分區,Kafka 將消息處理的壓力分攤到多個消息流中,從而獲得吞吐量的提升。此外,在分佈式場景下,通過將不同分區的消息分散在不同的機器上,能夠以水平擴展的方式容納和處理海量的數據。

前面提到,鍵在分區邏輯中作爲參考數據被使用。實際上,同一個鍵的消息將被分發到同一個分區,從而確保具有相同鍵的消息被同一個進程所處理。由於分佈式系統在處理數據是很難協同系統層面的統一狀態,很多傳統編程概念裏全局的狀態,典型的例如全局映射,實際上將在各個具體的工作機器上各不相同。通過將同一個鍵的消息分發到同一個分區,能夠保證在鍵這一量級上,代碼中的全局狀態對同一個鍵的所有消息在處理時都是同一個引用。這樣,我們才能夠進行有狀態的消息處理,而不是隻依賴於消息本身的無狀態的消息處理。有時候,業務層面的鍵值對和期望的分區處理的鍵存在阻抗失配的情形,此時可以通過業務層面將數據轉換成分區鍵到業務鍵到值的數據結構,或者自定義分區器( partitioner )來解決這個問題。

Kafka 社區經常使用流( stream )這個名詞來指代 Kafka 中同一個主題的消息。這裏的流是一組從生產者移動到消費者的數據,幾乎所有的流式處理框架都是這樣看待數據的。

模式

從數據的角度,還有一個對於 Kafka 來說外掛的邏輯值得一提,即消息的模式。上面提到 Kafka 中的消息是無具體含義的字節數組,這實際上要求消費者知道生產者是如何上報數據的;反過來說,消息的生產者不能自由地改變消息發佈的模式,而是要等待消費者發佈版本兼容兩種模式之後才能灰度發佈生產者的新版本。爲了處理這個問題,Kafka 支持模式註冊( scheme registry )功能。通常,開發者使用 Apache Avro 序列化框架來定義消息的模式,從而使得生產者按照 Avro 或其他序列化框架支持的模式演進規則來改變消息模式。在 Avro 的例子裏,模式的演進發生在模式文件的配置上,因此不需要改變代碼。對於消息的消費者來說,按照演進規則改變的消息模式能夠良好的向前及向後兼容,不會出現模式演進導致消費端消費失敗的情況。消費端需要利用新模式時,只需要對應更新模式文件。

Kafka 系統架構

Kafka 的系統架構包括四個主要組成部分

  1. Kafka 服務器 Broker
  2. 消息的生產者 Producer
  3. 消息的消費者 Consumer
  4. 元數據管理 ZooKeeper

元數據管理 ZooKeeper

其中 ZooKeeper 作爲外部依賴用於保存 Kafka 集羣的元數據信息,包括分區的消費位點( offset )等元數據和 Broker 選舉的元數據。早期還保存了消費者的消費位點等元數據,但在新版本中這一職責已經交給了 Broker 來管理。Kafka 源代碼中 kafka.zookeeper 和 kafka.zk 包下的內容封裝了所有跟 ZooKeeper 打交道的代碼。值得一提的是,Kafka 社區正在實現中的 KIP-500 提議致力於在 Kafka 之內實現管理元數據的 Controller Quroum 機制以移除對 ZooKeeper 的依賴。

Kafka 服務器 Broker

一臺 Kafka 服務器就是一個 Broker,一個 Kafka 集羣由多個 Broker 組成,一個 Broker 能夠支持多個分區。Broker 接收來自生產者的消息,撥動消費位點並將消息提交到磁盤保存。Broker 同時向消費者提供服務,對消費者讀取分區消息的請求做出相應,返回已經提交到磁盤上的消息。

在集羣中,一個分區歸屬於一個 Broker,該 Broker 稱爲分區的首領。一個分區可以分配給多個 Broker,此時將發生分區複製,從而製造消息冗餘以容忍某個 Broker 的故障。在首領 Broker 故障時,其它分配到其分區的 Broker 可以成爲新的首領並在消費者和生產者連接到新首領之後恢復消息服務。

每個集羣的 Broker 當中會有一個被選舉成爲集羣控制器( Controller )。它和 ZK 交互,負責集羣元數據的管理,包括將分區分配給具體的 broker 和監控 broker 等。如前所述,這個從 Broker 中選取 Controller 的邏輯將被替換成專門的 Controller Quorum 機制,即 Broker 不再兼任 Controller 的角色。

Kafka 消息的生產者 Producer

消息生產者 Producer 將消息發佈到指定的主題中。消息發佈的時候支持設定相應的元數據,例如消息的鍵和用於確定消息分區的分區器。

Kafka 消息的消費者 Consumer

消息消費者 Consumer 向 Broker 請求特定的一個或多個主題的消息,根據消息的分區索引以及對應分區的消費位點獲取實際的當前消費消息。

消費者將歸屬於某個消費組,也就是說,會有一個或多個消費者讀取同一個主題。Kafka 保證每個分區在同一個消費組裏只能被同一個消費者使用,從而保證消息的消費壓力被平攤到消費組內的消費者上。而且,當一個消費者失效時,通過再平衡機制能夠將其消費工作攤派到其他消費者之上,從而達到消費者層面的容錯。

在實際生產中,一個常見的問題就是同一個業務方的不同業務邏輯要重複消費同一份消息。由於消費組的申請在公司中通常是需要審批,缺乏經驗的開發者會簡單的在原先的消費組上新增一個消費者並期待它能夠完整的消費一份重複的數據。通過上面的介紹,很顯然這隻會引起消費組內分區和消費者對應關係的再平衡。爲了重複同一份消息,應該重新申請一個消費組消費同一個主題的數據。可以說,消費組是業務層面的隔離機制。

一個典型的 Kafka 集羣的拓撲結構如下圖所示。

kafka-arch

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