入門篇!大白話帶你認(rèn)識 Kafka!
前言“Kafka 是我在疫情期間在游戲之余學(xué)的。雖然之前用過 ActiveMQ 和 RabbitMQ,但是在 Kafka 這門技術(shù)面前我也算是一個初學(xué)者。文章中若有說法有點完善或者不準(zhǔn)確的地方敬請指出。”今天我們來聊聊 Kafka ,主要是帶你重新認(rèn)識一下 Kafka,聊一下 Kafka 中比較重要的概念和問題。在后面的文章中我會介紹:
- Kafka 的一些高級特性比如工作流程。
- 使用 Docker 安裝 Kafka 并簡單使用其發(fā)送和消費消息。
- Spring Boot 程序如何使用 Kafka 作為消息隊列。
我們現(xiàn)在經(jīng)常提到 Kafka 的時候就已經(jīng)默認(rèn)它是一個非常優(yōu)秀的消息隊列了,我們也會經(jīng)常拿它給 RocketMQ、RabbitMQ 對比。我覺得 Kafka 相比其他消息隊列主要的優(yōu)勢如下:
- 極致的性能 :基于 Scala 和 Java 語言開發(fā),設(shè)計中大量使用了批量處理和異步的思想,最高可以每秒處理千萬級別的消息。
- 生態(tài)系統(tǒng)兼容性無可匹敵 :Kafka 與周邊生態(tài)系統(tǒng)的兼容性是最好的沒有之一,尤其在大數(shù)據(jù)和流計算領(lǐng)域。
實際上在早期的時候 Kafka 并不是一個合格的消息隊列,早期的 Kafka 在消息隊列領(lǐng)域就像是一個衣衫襤褸的孩子一樣,功能不完備并且有一些小問題比如丟失消息、不保證消息可靠性等等。當(dāng)然,這也和 LinkedIn 最早開發(fā) Kafka 用于處理海量的日志有很大關(guān)系,哈哈哈,人家本來最開始就不是為了作為消息隊列滴,誰知道后面誤打誤撞在消息隊列領(lǐng)域占據(jù)了一席之地。
隨著后續(xù)的發(fā)展,這些短板都被 Kafka 逐步修復(fù)完善。所以**,Kafka 作為消息隊列不可靠這個說法已經(jīng)過時!**
初識 Kafka
先來看一下官網(wǎng)對其的介紹,應(yīng)該是最權(quán)威和實時的了。是英文也沒有關(guān)系,我已經(jīng)將比較重要的信息都為你提取出來了。
從官方介紹中我們可以得到以下信息:
Kafka 是一個分布式流式處理平臺。這到底是什么意思呢?
流平臺具有三個關(guān)鍵功能:
- 消息隊列:發(fā)布和訂閱消息流,這個功能類似于消息隊列,這也是 Kafka 也被歸類為消息隊列的原因。
- 容錯的持久方式存儲記錄消息流:Kafka 會把消息持久化到磁盤,有效避免了消息丟失的風(fēng)險·。
- 流式處理平臺: 在消息發(fā)布的時候進(jìn)行處理,Kafka 提供了一個完整的流式處理類庫。
Kafka 主要有兩大應(yīng)用場景:
- 消息隊列 :建立實時流數(shù)據(jù)管道,以可靠地在系統(tǒng)或應(yīng)用程序之間獲取數(shù)據(jù)。
- 數(shù)據(jù)處理: 構(gòu)建實時的流數(shù)據(jù)處理程序來轉(zhuǎn)換或處理數(shù)據(jù)流。
關(guān)于 Kafka 幾個非常重要的概念:
- Kafka 將記錄流(流數(shù)據(jù))存儲在 topic 中。
- 每個記錄由一個鍵、一個值、一個時間戳組成。
Kafka 消息模型
“題外話:早期的 JMS 和 AMQP 屬于消息服務(wù)領(lǐng)域權(quán)威組織所做的相關(guān)的標(biāo)準(zhǔn),我在 JavaGuide的 《消息隊列其實很簡單》這篇文章中介紹過。但是,這些標(biāo)準(zhǔn)的進(jìn)化跟不上消息隊列的演進(jìn)速度,這些標(biāo)準(zhǔn)實際上已經(jīng)屬于廢棄狀態(tài)。所以,可能存在的情況是:不同的消息隊列都有自己的一套消息模型。
”隊列模型:早期的消息模型
使用隊列(Queue)作為消息通信載體,滿足生產(chǎn)者與消費者模式,一條消息只能被一個消費者使用,未被消費的消息在隊列中保留直到被消費或超時。 比如:我們生產(chǎn)者發(fā)送 100 條消息的話,兩個消費者來消費一般情況下兩個消費者會按照消息發(fā)送的順序各自消費一半(也就是你一個我一個的消費。)
隊列模型存在的問題
假如我們存在這樣一種情況:我們需要將生產(chǎn)者產(chǎn)生的消息分發(fā)給多個消費者,并且每個消費者都能接收到完成的消息內(nèi)容。
這種情況,隊列模型就不好解決了。很多比較杠精的人就說:我們可以為每個消費者創(chuàng)建一個單獨的隊列,讓生產(chǎn)者發(fā)送多份。這是一種非常愚蠢的做法,浪費資源不說,還違背了使用消息隊列的目的。
發(fā)布-訂閱模型:Kafka 消息模型
發(fā)布-訂閱模型主要是為了解決隊列模型存在的問題。
發(fā)布訂閱模型(Pub-Sub) 使用主題(Topic) 作為消息通信載體,類似于廣播模式;發(fā)布者發(fā)布一條消息,該消息通過主題傳遞給所有的訂閱者,在一條消息廣播之后才訂閱的用戶則是收不到該條消息的。
在發(fā)布 - 訂閱模型中,如果只有一個訂閱者,那它和隊列模型就基本是一樣的了。所以說,發(fā)布 - 訂閱模型在功能層面上是可以兼容隊列模型的。
Kafka 采用的就是發(fā)布 - 訂閱模型。 如下圖所示:
“RocketMQ 的消息模型和 Kafka 基本是完全一樣的。唯一的區(qū)別是 RocketMQ 中沒有隊列這個概念,與之對應(yīng)的是 Partion(分區(qū))。
”Kafka 重要概念解讀
Kafka 將生產(chǎn)者發(fā)布的消息發(fā)送到 Topic(主題) 中,需要這些消息的消費者可以訂閱這些 Topic(主題),如下圖所示:
Kafka Topic Partition
上面這張圖也為我們引出了,Kafka 比較重要的幾個概念:
- Producer(生產(chǎn)者) : 產(chǎn)生消息的一方。
- Consumer(消費者) : 消費消息的一方。
- Broker(代理) : 可以看作是一個獨立的 Kafka 實例。多個 Kafka Broker 組成一個 Kafka Cluster。
同時,你一定也注意到每個 Broker 中又包含了 Topic 以及 Partion 這兩個重要的概念:
- Topic(主題) : Producer 將消息發(fā)送到特定的主題,Consumer 通過訂閱特定的 Topic(主題) 來消費消息。
- Partion(分區(qū)) : Partion 屬于 Topic 的一部分。一個 Topic 可以有多個 Partion ,并且同一 Topic 下的 Partion 可以分布在不同的 Broker 上,這也就表明一個 Topic 可以橫跨多個 Broker 。這正如我上面所畫的圖一樣。
“劃重點:Kafka 中的 Partion(分區(qū)) 實際上可以對應(yīng)成為消息隊列中的隊列。這樣是不是更好理解一點?”
另外,還有一點我覺得比較重要的是 Kafka 為分區(qū)(Partion)引入了多副本(Replica)機制。分區(qū)(Partion)中的多個副本之間會有一個叫做 leader 的家伙,其他副本稱為 follower。我們發(fā)送的消息會被發(fā)送到 leader 副本,然后 follower 副本才能從 leader 副本中拉取消息進(jìn)行同步。
“生產(chǎn)者和消費者只與 leader 副本交互。你可以理解為其他副本只是 leader 副本的拷貝,它們的存在只是為了保證消息存儲的安全性。當(dāng) leader 副本發(fā)生故障時會從 follower 中選舉出一個 leader,但是 follower 中如果有和 leader 同步程度達(dá)不到要求的參加不了 leader 的競選。
”Kafka 的多分區(qū)(Partition)以及多副本(Replica)機制有什么好處呢?
- Kafka 通過給特定 Topic 指定多個 Partition, 而各個 Partition 可以分布在不同的 Broker 上, 這樣便能提供比較好的并發(fā)能力(負(fù)載均衡)。
- Partition 可以指定對應(yīng)的 Replica 數(shù), 這也極大地提高了消息存儲的安全性, 提高了容災(zāi)能力,不過也相應(yīng)的增加了所需要的存儲空間。
Zookeeper 在 Kafka 中的作用
“要想搞懂 zookeeper 在 Kafka 中的作用 一定要自己搭建一個 Kafka 環(huán)境然后自己進(jìn) zookeeper 去看一下有哪些文件夾和 Kafka 有關(guān),每個節(jié)點又保存了什么信息。 一定不要光看不實踐,這樣學(xué)來的也終會忘記!”
后面的文章中會介紹如何搭建 Kafka 環(huán)境,你且不要急,看了后續(xù)文章 3 分鐘就能搭建一個 Kafka 環(huán)境。
“這部分內(nèi)容參考和借鑒了這篇文章:https://www.jianshu.com/p/a036405f989c 。”
下圖就是我的本地 Zookeeper ,它成功和我本地的 Kafka 關(guān)聯(lián)上(以下文件夾結(jié)構(gòu)借助 idea 插件 Zookeeper tool 實現(xiàn))。
ZooKeeper 主要為 Kafka 提供元數(shù)據(jù)的管理的功能。
從圖中我們可以看出,Zookeeper 主要為 Kafka 做了下面這些事情:
- Broker 注冊 :在 Zookeeper 上會有一個專門用來進(jìn)行 Broker 服務(wù)器列表記錄的節(jié)點。每個 Broker 在啟動時,都會到 Zookeeper 上進(jìn)行注冊,即到/brokers/ids 下創(chuàng)建屬于自己的節(jié)點。每個 Broker 就會將自己的 IP 地址和端口等信息記錄到該節(jié)點中去
- Topic 注冊 :在 Kafka 中,同一個Topic 的消息會被分成多個分區(qū)并將其分布在多個 Broker 上,這些分區(qū)信息及與 Broker 的對應(yīng)關(guān)系也都是由 Zookeeper 在維護(hù)。比如我創(chuàng)建了一個名字為 my-topic 的主題并且它有兩個分區(qū),對應(yīng)到 zookeeper 中會創(chuàng)建這些文件夾:/brokers/topics/my-topic/partions/0、/brokers/topics/my-topic/partions/1
- 負(fù)載均衡 :上面也說過了 Kafka 通過給特定 Topic 指定多個 Partition, 而各個 Partition 可以分布在不同的 Broker 上, 這樣便能提供比較好的并發(fā)能力。對于同一個 Topic 的不同 Partition,Kafka 會盡力將這些 Partition 分布到不同的 Broker 服務(wù)器上。當(dāng)生產(chǎn)者產(chǎn)生消息后也會盡量投遞到不同 Broker 的 Partition 里面。當(dāng) Consumer 消費的時候,Zookeeper 可以根據(jù)當(dāng)前的 Partition 數(shù)量以及 Consumer 數(shù)量來實現(xiàn)動態(tài)負(fù)載均衡。
- ......
Kafka 如何保證消息的消費順序?
我們在使用消息隊列的過程中經(jīng)常有業(yè)務(wù)場景需要嚴(yán)格保證消息的消費順序,比如我們同時發(fā)了 2 個消息,這 2 個消息對應(yīng)的操作分別對應(yīng)的數(shù)據(jù)庫操作是:更改用戶會員等級、根據(jù)會員等級計算訂單價格。假如這兩條消息的消費順序不一樣造成的最終結(jié)果就會截然不同。
我們知道 Kafka 中 Partition(分區(qū))是真正保存消息的地方,我們發(fā)送的消息都被放在了這里。而我們的 Partition(分區(qū)) 又存在于 Topic(主題) 這個概念中,并且我們可以給特定 Topic 指定多個 Partition。
Kafka Topic Partions Layout
每次添加消息到 Partition(分區(qū)) 的時候都會采用尾加法,如上圖所示。Kafka 只能為我們保證 Partition(分區(qū)) 中的消息有序,而不能保證 Topic(主題) 中的 Partition(分區(qū)) 的有序。
“消息在被追加到 Partition(分區(qū))的時候都會分配一個特定的偏移量(offset)。Kafka 通過偏移量(offset)來保證消息在分區(qū)內(nèi)的順序性。”
所以,我們就有一種很簡單的保證消息消費順序的方法:1 個 Topic 只對應(yīng)一個 Partion。這樣當(dāng)然可以解決問題,但是破壞了 Kafka 的設(shè)計初衷。
Kafka 中發(fā)送 1 條消息的時候,可以指定 topic, partition, key,data(數(shù)據(jù)) 4 個參數(shù)。如果你發(fā)送消息的時候指定了 partion 的話,所有消息都會被發(fā)送到指定的 partion。并且,同一個 key 的消息可以保證只發(fā)送到同一個 partition,這個我們可以采用表/對象的 id 來作為 key 。
總結(jié)一下,對于如何保證 Kafka 中消息消費的順序,有了下面兩種方法:
- 1 個 Topic 只對應(yīng)一個 Partion。
- (推薦)發(fā)送消息的時候指定 key/partion。
當(dāng)然不僅僅只有上面兩種方法,上面兩種方法是我覺得比較好理解的,