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