Apache Kafka 的基本概念

基本概念

主題 Topic

topic 是 Kafka 最基礎的組織單位,類似於關係數據庫中的數據表。做爲使用 kafka 的開發者,你最應該考慮的是和 topoc 相關的抽象。創建不同的 topic 保存不同種類的 events,或者通過不同的 topic 保存各種版本經過過濾、轉換後的同類 events。

topic 就是一連串 event 日誌(相對 queue 來說,沒有索引,不允許從中間插入,只能通過追加的方式寫入)。對於日誌來說,語義簡單,數據結構也簡單。第一,日誌只支持追加寫入:當在日誌中寫入消息時,總是寫在末端。第二,讀取日誌在日誌中搜索偏移量(offset),然後掃描有序的日誌條目。第三,日誌中一系列事件具有不可變性——覆水難收。日誌簡單的語義,使其在 Kafka 中,進出 topic 時能保持較高的吞吐量,並且意味着複製 topic 將更加容易。

日誌也是基本的持久化手段。傳統企業的消息發送系統有 topic 和 queue,該系統在發送者和接受者之間暫時緩存消息。

因爲 Kafka 的 topic 是日誌,所以在 topic 中的數據不存在暫存的問題。每個 topic 都可以配置數據的有效期(或者固定大小),從幾秒鐘到幾年,甚至無限期。構成 Kafka topic 的日誌做爲文件存儲在磁盤上。當你向 topic 中寫入事件時,該事件的持久性就像你將它寫入你信任的數據庫一樣。

簡單的日誌、內容的不可變性是 Kafka 在現代數據基礎架構中,成功的關鍵——但這些只是開始。

Kafka 分區

如果一個 topic 只能在一臺機器上存活,將極大的限制 Kafka 的可擴展性。Kafka 可以跨多臺機器管理多個 topic——畢竟,Kafka 是分佈式系統——但沒有哪個主題能夠無限制的變大或者容納太多的讀寫。幸運的是,Kafka 賦予了我們將 topic 分區的能力。

分區將 topic 的單個日誌文件拆分爲多個日誌文件,每一個日誌文件在 Kafka 集羣中,隔離的節點上都能存活。這樣一來,存儲消息、寫入消息、處理已有的消息都能分離到集羣的節點中。

Kafka 如何分區

如果將 topic 拆分到各個分區,我們需要將消息分配至分區的方法。通常而言,如果消息沒有 key,消息將通過輪詢的方式寫入 topic 的各個分區中。在這種情況下,所有分區平均分配數據,但是我們不保留寫入的消息順序。如果消息有 key,將會通過 key 的哈希值計算出目標分區。這使得 Kafka 能保證相同的 key 寫入同一個分區,並且有序。

例如,如果你生成一些和同一個客戶相關的事件,使用客戶的 ID 做爲 key 將保證來自客戶的所有事件是有序的。但是這可能會導致,一個非常活躍的 key 創建一個又大、又活躍的分區,但是在實踐中,這種風險很小,並且出現這種情況時,是可控的。大多數情況下,保持 key 的有序性是值得的。

Kafka Brokers

目前爲止,我們討論了事件、主題、分區,但是還沒有提到架構中的計算機。從物理基礎設施來說,Kafka 是由一個機器(稱爲 broker)網絡組成的。就目前的部署方式來說,這些 broker 可能不會部署在物理隔離的服務器上,而是基於 pod 部署在容器中,而這些 pod 又是基於虛擬服務器運行,虛擬服務器基於某個物理數據中的核心運行。無論如何,這些 broker 部署後,它們之間互相獨立運行 Kafka broker 進程。每個 broker 持有一些分區,並且處理請求以寫入新的事件到那些分區中或者從那些分區中讀取事件。broker 同時處理分區之間的 replication。

Replication

如果我們只有一個 broker 保存 partition,那麼 replication 將不工作。無論 broker 是部署在裸機上還是託管在容器中,它們以及它們潛在的存儲都可能受到宕機的影響,所以我們需要將分區的數據拷貝至幾個 broker 中,從而保證數據的安全性。那些拷貝叫做從副本,反之,主分區叫做主副本。當你生產數據到主分區中時——一般而言,讀和寫都是通過主分區完成的——主分區和從分區同時工作,複製那些新的寫入到從分區中。

這個過程是自動的,而你可以在生產者中通過設置生成各種層級的持久化擔保,這個不是開發者基於 Kafka 構建系統時都需要考慮的工作。做爲開發者,你只需要知道你的數據是安全的,如果集羣中的某個節點掛了,另一個節點將完全接手它的工作。

客戶端應用程序

現在,我們脫離 Kafka 集羣本身不談,看看使用 Kafka 的應用程序:生產者和消費者。這些客戶端應用程序,包含將消息放入 topic 中,從 topic 中讀取消息的代碼。Kafka 平臺中的所有組件,除 broker 外,要麼是生產者,要麼是消費者,或者兩者兼備。生產和消費是你與集羣的交互方式。

Kafka 生產者

生產者庫的暴露的 API 相當輕量化:在 Java 中,有一個叫做 KafkaProducer 的類,你可以用其連接集羣。使用 Map 來配置參數,包括集羣中的幾個 broker 的地址,可用的安全配置,以及其他決定生產者網絡行爲的配置。還有一個類叫做 ProduceRecord,用於持有你要發送到集羣中的鍵值對。

大致來說,這就是生產消息所需的所有 API。從底層來說,該庫管理了鏈接池、網絡緩衝、等待 broker 確認消息、在必要的時候轉發消息、以及一些開發者不需要關心的主機相關的 host 細節。

Kafka 消費者

消費者 API 原則上和生產者差不多。使用一個叫做 KafkaConsumer 的類連接集羣(使用 Map 配置集羣地址、安全性、以及其他參數)。然後使用該鏈接訂閱多個 topic。當 topic 中有可用的消息時,客戶端以 ConsumerRecord 的格式接收消息,並保存在 ConsumerRecords 集合中。一個 ConsumerRecod 對象代表一條 Kafka 消息的鍵值對。

KafkaConsumer 管理鏈接池以及網絡協議,就和 KafkaProducer 一樣,但是在讀取方面,除了網絡管道之外,還有更多的內容。首當其衝的是,Kafka 與其他消息隊列不同,只是讀取消息,不銷燬消息;其他消費者仍然可以消費那些消息。事實上,在 Kafka 中,多個消費者從一個 topic 中消費信息非常正常。這個小事實對結合 Kafka 的軟件架構來說,產生了極大的影響。

同時,消費者需要處理以下這種情況,某個主題的消費速率,結合處理單條消息的計算成本後,單個實例無法承受。也就是說,需要擴展消費者。在 Kafka 中,擴展消費者組或多或少是自動的。

Kafka 組件、生態系統

如果你的系統只是隨着生產者(寫事件)和消費者(讀事件)的不斷增長 ,通過 broker 管理分區、複製 topic,那麼你的系統將是一個非常有用的系統。相比之下,Kafka 社區的經驗表明,某些模式顯現出來,並促使你和你的組員圍繞核心 Kafka 重複的構建相同的功能。

最終,你將構建一些公共的應用程序層,從而避免重複的無差別任務。這些代碼的能力非常重要,但是和你的業務完全無關。並且對你的用戶不產生直接價值。這些是基礎設施,並且應該由社區或者基礎設施供應商提供。

寫這些代碼可能很有誘惑力,但是你不應該這麼做。這類基礎設施代碼有:Kafka Connect、Confulent Schema Registry、Kafka Streams、ksqlDB。接下來,我們將逐一進行講解。

Kafk Connect

在存儲和檢索的世界裏,不都是使用 Kafka。有時候,你可能想把存儲在那些系統裏的數據存入 Kafka topic 中,有時候,你想把 Kafka topic 裏的數據存入那些系統中。做爲 Apache Kafka 的集成 API,Kafka Connect 負責這些能力。

Kafka Connect 做了哪些事情?

一方面,Kafka Connect 是可插拔連接器中的一個生態系統,另一方面,它也是一個客戶端應用程序。做爲客戶端應用程序,Connect 是一個直接在硬件上運行的服務端進程,獨立於 Kafka broker。Connect 可擴展並且容錯,這表明你不僅可以運行單一的 Connect worker,還可以運行 Connect worker 集羣,這些 worker 共享從外部系統進出 Kafka 的負載。 Kafka Connect 同時抽象了編碼,只需要使用 JSON 配置即可運行。以下從 Kafka 到 Elasticsearch 的流數據配置實例:

{
  "connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector",
  "topics"         : "my_topic",
  "connection.url" : "http://elasticsearch:9200",
  "type.name"      : "_doc",
  "key.ignore"     : "true",
  "schema.ignore"  : "true"
}

Kafka Connect 的優勢

Kafka Connect 最主要的優勢是連接器的強大生態。將數據寫入雲存儲、ES、或者關係數據庫中的代碼,在不同的業務之間,不會產生很大的變化。同樣,從關係數據庫、Salesforce、或者遺留 HDFS 文件系統讀取數據也是相似的。你可以自己編寫這部分代碼,但是花費的時間並不會爲你的客戶帶來獨特的價值,也不會讓你的業務更有競爭力。

Kafka Streams

在一個持續更新、集成了 Kafka 的應用程序中,消費者會變得越來越複雜。剛開始的時候,可能只是一個簡單的無狀態轉換器(例如,個人信息脫敏、轉換消息格式),不久後,集成進複雜的聚合、填充數據等操作。如果你回顧我們上面提到的消費者代碼,就會發現 API 不支持那些操作:你將要構建很多框架代碼來處理時間窗口、延遲消息、查找數據表、通過 Key 做集成,等等。等你實現那些代碼後,你會發現像聚合、填充數據這類操作通常是有狀態的。

“狀態”需要保存在程序的堆中,這就意味着它的容錯債務。如果你的流處理應用程序下線了,應用程序中的狀態也就丟失了,除非你設計了一張表來保存狀態。在可擴展的情況下,編寫、調試這類事情並不簡單,同時也不會讓你的用戶受益。這就是爲什麼 Apache Kafka 提供流處理 API 的原因。也是爲什麼需要 Kafka Streams 的原因。

Kafka Streams 是什麼

Kafka Streams 是一個 Java API,在訪問流處理計算原語時,爲你提供便利性:過濾、分組、聚合、連接,等等,也不需要你基於消費者 API 編寫框架代碼。同時支持流處理計算產生的大量狀態。如果你對一個吞吐量較高的 topic ,通過某個字段做事件分組,然後每個小時對分組數據做一次彙總,那麼你可能需要很大的內存。

確實,對於數據量較大的 topic,複雜的流處理拓撲圖來說,不難想象,你必須要部署一個集羣來共享流處理負載,就和消費組一樣。Steams API 爲你解決了分佈式狀態的問題:它將狀態持久化至本地磁盤和 Kafka 集羣的 topic 中,並且在流處理集羣中添加或移除流處理節點時,在多節點間自動分配狀態。

在一個典型的微服務中,流處理是其他功能的附加產物。例如,發貨通知服務可能會將產品信息變更日誌(包括客戶記錄)結合起來,生成發貨通知對象,其他服務將其轉換爲電子郵件或者短信。但是,當爲移動 App 或者 Web 前端展示發貨狀態時,發貨通知服務也有義務通過 REST API 封裝同步關鍵字檢索接口。

該服務是基於事件響應的服務——在這種情況下,連接(join) 3 個流,可能還要基於連接(join)結果做窗口計算——但是它也提供了基於 REST 接口提供的 HTTP 服務,可能基於 Spring 框架或者 Micronaut, 或者其他常用的 Java API。鑑於 Kafka Streams 是一個只做流處理的 Java 庫,而不是一組專門做流處理的基礎組件,所以,使用其他框架實現 REST 接口,以及複雜的、可擴展的、容錯的流處理非常容易。

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