前言
Kafka 是我在疫情期間在遊戲之餘學的。雖然之前用過 ActiveMQ 和 RabbitMQ,但是在 Kafka 這門技術面前我也算是一個初學者。文章中若有說法有點完善或者不準確的地方敬請指出。
今天我們來聊聊 Kafka ,主要是帶你重新認識一下 Kafka,聊一下 Kafka 中比較重要的概念和問題。在後面的文章中我會介紹:
- Kafka 的一些高級特性比如工作流程。
- 使用 Docker 安裝 Kafka 並簡單使用其發送和消費消息。
- Spring Boot 程序如何使用 Kafka 作爲消息隊列。
我們現在經常提到 Kafka 的時候就已經默認它是一個非常優秀的消息隊列了,我們也會經常拿它給 RocketMQ、RabbitMQ 對比。我覺得 Kafka 相比其他消息隊列主要的優勢如下:
- 極致的性能 :基於 Scala 和 Java 語言開發,設計中大量使用了批量處理和異步的思想,最高可以每秒處理千萬級別的消息。
- 生態系統兼容性無可匹敵 :Kafka 與周邊生態系統的兼容性是最好的沒有之一,尤其在大數據和流計算領域。
實際上在早期的時候 Kafka 並不是一個合格的消息隊列,早期的 Kafka 在消息隊列領域就像是一個衣衫襤褸的孩子一樣,功能不完備並且有一些小問題比如丟失消息、不保證消息可靠性等等。當然,這也和 LinkedIn 最早開發 Kafka 用於處理海量的日誌有很大關係,哈哈哈,人家本來最開始就不是爲了作爲消息隊列滴,誰知道後面誤打誤撞在消息隊列領域佔據了一席之地。
隨着後續的發展,這些短板都被 Kafka 逐步修復完善。所以,Kafka 作爲消息隊列不可靠這個說法已經過時!
初識 Kafka
先來看一下官網對其的介紹,應該是最權威和實時的了。是英文也沒有關係,我已經將比較重要的信息都爲你提取出來了。
從官方介紹中我們可以得到以下信息:
Kafka 是一個分佈式流式處理平臺。這到底是什麼意思呢?
流平臺具有三個關鍵功能:
- 消息隊列:發佈和訂閱消息流,這個功能類似於消息隊列,這也是 Kafka 也被歸類爲消息隊列的原因。
- 容錯的持久方式存儲記錄消息流: Kafka 會把消息持久化到磁盤,有效避免了消息丟失的風險·。
- 流式處理平臺: 在消息發佈的時候進行處理,Kafka 提供了一個完整的流式處理類庫。
Kafka 主要有兩大應用場景:
- 消息隊列 :建立實時流數據管道,以可靠地在系統或應用程序之間獲取數據。
- 數據處理: 構建實時的流數據處理程序來轉換或處理數據流。
關於 Kafka 幾個非常重要的概念:
- Kafka 將記錄流(流數據)存儲在
topic
中。 - 每個記錄由一個鍵、一個值、一個時間戳組成。
Kafka 消息模型
題外話:早期的 JMS 和 AMQP 屬於消息服務領域權威組織所做的相關的標準,我在 JavaGuide的 《消息隊列其實很簡單》這篇文章中介紹過。但是,這些標準的進化跟不上消息隊列的演進速度,這些標準實際上已經屬於廢棄狀態。所以,可能存在的情況是:不同的消息隊列都有自己的一套消息模型。
隊列模型:早期的消息模型
使用隊列(Queue)作爲消息通信載體,滿足生產者與消費者模式,一條消息只能被一個消費者使用,未被消費的消息在隊列中保留直到被消費或超時。 比如:我們生產者發送 100 條消息的話,兩個消費者來消費一般情況下兩個消費者會按照消息發送的順序各自消費一半(也就是你一個我一個的消費。)
隊列模型存在的問題
假如我們存在這樣一種情況:我們需要將生產者產生的消息分發給多個消費者,並且每個消費者都能接收到完成的消息內容。
這種情況,隊列模型就不好解決了。很多比較槓精的人就說:我們可以爲每個消費者創建一個單獨的隊列,讓生產者發送多份。這是一種非常愚蠢的做法,浪費資源不說,還違背了使用消息隊列的目的。
發佈-訂閱模型:Kafka 消息模型
發佈-訂閱模型主要是爲了解決隊列模型存在的問題。
發佈訂閱模型(Pub-Sub) 使用主題(Topic) 作爲消息通信載體,類似於廣播模式;發佈者發佈一條消息,該消息通過主題傳遞給所有的訂閱者,在一條消息廣播之後才訂閱的用戶則是收不到該條消息的。
在發佈 - 訂閱模型中,如果只有一個訂閱者,那它和隊列模型就基本是一樣的了。所以說,發佈 - 訂閱模型在功能層面上是可以兼容隊列模型的。
Kafka 採用的就是發佈 - 訂閱模型。
RocketMQ 的消息模型和 Kafka 基本是完全一樣的。唯一的區別是 Kafka 中沒有隊列這個概念,與之對應的是 Partition(分區)。
Kafka 重要概念解讀
Kafka 將生產者發佈的消息發送到 Topic(主題) 中,需要這些消息的消費者可以訂閱這些 Topic(主題),如下圖所示:
上面這張圖也爲我們引出了,Kafka 比較重要的幾個概念:
- Producer(生產者) : 產生消息的一方。
- Consumer(消費者) : 消費消息的一方。
- Broker(代理) : 可以看作是一個獨立的 Kafka 實例。多個 Kafka Broker 組成一個 Kafka Cluster。
同時,你一定也注意到每個 Broker 中又包含了 Topic 以及 Partition 這兩個重要的概念:
- Topic(主題) : Producer 將消息發送到特定的主題,Consumer 通過訂閱特定的 Topic(主題) 來消費消息。
- Partition(分區) : Partition 屬於 Topic 的一部分。一個 Topic 可以有多個 Partition ,並且同一 Topic 下的 Partition 可以分佈在不同的 Broker 上,這也就表明一個 Topic 可以橫跨多個 Broker 。這正如我上面所畫的圖一樣。
劃重點:Kafka 中的 Partition(分區) 實際上可以對應成爲消息隊列中的隊列。這樣是不是更好理解一點?
另外,還有一點我覺得比較重要的是 Kafka 爲分區(Partition)引入了多副本(Replica)機制。分區(Partition)中的多個副本之間會有一個叫做 leader 的傢伙,其他副本稱爲 follower。我們發送的消息會被髮送到 leader 副本,然後 follower 副本才能從 leader 副本中拉取消息進行同步。
生產者和消費者只與 leader 副本交互。你可以理解爲其他副本只是 leader 副本的拷貝,它們的存在只是爲了保證消息存儲的安全性。當 leader 副本發生故障時會從 follower 中選舉出一個 leader,但是 follower 中如果有和 leader 同步程度達不到要求的參加不了 leader 的競選。
Kafka 的多分區(Partition)以及多副本(Replica)機制有什麼好處呢?
- Kafka 通過給特定 Topic 指定多個 Partition, 而各個 Partition 可以分佈在不同的 Broker 上, 這樣便能提供比較好的併發能力(負載均衡)。
- Partition 可以指定對應的 Replica 數, 這也極大地提高了消息存儲的安全性, 提高了容災能力,不過也相應的增加了所需要的存儲空間。
Zookeeper 在 Kafka 中的作用
要想搞懂 zookeeper 在 Kafka 中的作用 一定要自己搭建一個 Kafka 環境然後自己進 zookeeper 去看一下有哪些文件夾和 Kafka 有關,每個節點又保存了什麼信息。 一定不要光看不實踐,這樣學來的也終會忘記!
後面的文章中會介紹如何搭建 Kafka 環境,你且不要急,看了後續文章 3 分鐘就能搭建一個 Kafka 環境。
這部分內容參考和借鑑了這篇文章:https://www.jianshu.com/p/a036405f989c 。
下圖就是我的本地 Zookeeper ,它成功和我本地的 Kafka 關聯上(以下文件夾結構藉助 idea 插件 Zookeeper tool 實現)。
ZooKeeper 主要爲 Kafka 提供元數據的管理的功能。
從圖中我們可以看出,Zookeeper 主要爲 Kafka 做了下面這些事情:
- Broker 註冊 :在 Zookeeper 上會有一個專門用來進行 Broker 服務器列表記錄的節點。每個 Broker 在啓動時,都會到 Zookeeper 上進行註冊,即到/brokers/ids 下創建屬於自己的節點。每個 Broker 就會將自己的 IP 地址和端口等信息記錄到該節點中去
- Topic 註冊 : 在 Kafka 中,同一個Topic 的消息會被分成多個分區並將其分佈在多個 Broker 上,這些分區信息及與 Broker 的對應關係也都是由 Zookeeper 在維護。比如我創建了一個名字爲 my-topic 的主題並且它有兩個分區,對應到 zookeeper 中會創建這些文件夾:
/brokers/topics/my-topic/Partitions/0
、/brokers/topics/my-topic/Partitions/1
- 負載均衡 :上面也說過了 Kafka 通過給特定 Topic 指定多個 Partition, 而各個 Partition 可以分佈在不同的 Broker 上, 這樣便能提供比較好的併發能力。 對於同一個 Topic 的不同 Partition,Kafka 會盡力將這些 Partition 分佈到不同的 Broker 服務器上。當生產者產生消息後也會盡量投遞到不同 Broker 的 Partition 裏面。當 Consumer 消費的時候,Zookeeper 可以根據當前的 Partition 數量以及 Consumer 數量來實現動態負載均衡。
- …
Kafka 如何保證消息的消費順序?
我們在使用消息隊列的過程中經常有業務場景需要嚴格保證消息的消費順序,比如我們同時發了 2 個消息,這 2 個消息對應的操作分別對應的數據庫操作是:更改用戶會員等級、根據會員等級計算訂單價格。假如這兩條消息的消費順序不一樣造成的最終結果就會截然不同。
我們知道 Kafka 中 Partition(分區)是真正保存消息的地方,我們發送的消息都被放在了這裏。而我們的 Partition(分區) 又存在於 Topic(主題) 這個概念中,並且我們可以給特定 Topic 指定多個 Partition。
每次添加消息到 Partition(分區) 的時候都會採用尾加法,如上圖所示。Kafka 只能爲我們保證 Partition(分區) 中的消息有序,而不能保證 Topic(主題) 中的 Partition(分區) 的有序。
消息在被追加到 Partition(分區)的時候都會分配一個特定的偏移量(offset)。Kafka 通過偏移量(offset)來保證消息在分區內的順序性。
所以,我們就有一種很簡單的保證消息消費順序的方法:1 個 Topic 只對應一個 Partition。這樣當然可以解決問題,但是破壞了 Kafka 的設計初衷。
Kafka 中發送 1 條消息的時候,可以指定 topic, partition, key,data(數據) 4 個參數。如果你發送消息的時候指定了 Partition 的話,所有消息都會被髮送到指定的 Partition。並且,同一個 key 的消息可以保證只發送到同一個 partition,這個我們可以採用表/對象的 id 來作爲 key 。
總結一下,對於如何保證 Kafka 中消息消費的順序,有了下面兩種方法:
- 1 個 Topic 只對應一個 Partition。
- (推薦)發送消息的時候指定 key/Partition。
當然不僅僅只有上面兩種方法,上面兩種方法是我覺得比較好理解的,
推薦閱讀
- Apache Kafka using Keys for Partition:https://linuxhint.com/apache_kafka_partitions/
- Spring Boot and Kafka – Practical Configuration Examples:https://thepracticaldeveloper.com/2018/11/24/spring-boot-kafka-config/
- 一文看懂大數據領域的六年鉅變:https://www.infoq.cn/article/b8*EMm6AeiHDfI3SfT11
開源項目推薦
作者的其他開源項目推薦:
- JavaGuide:【Java學習+面試指南】 一份涵蓋大部分Java程序員所需要掌握的核心知識。
- springboot-guide : 適合新手入門以及有經驗的開發人員查閱的 Spring Boot 教程(業餘時間維護中,歡迎一起維護)。
- programmer-advancement : 我覺得技術人員應該有的一些好習慣!
- spring-security-jwt-guide :從零入門 !Spring Security With JWT(含權限驗證)後端部分代碼。