大曝光!從RabbitMQ平滑遷移至Kafka架構(gòu)設(shè)計(jì)方案!
歷史原因,公司存在多個(gè) MQ 同時(shí)使用的問題,我們中間件團(tuán)隊(duì)在去年下半年開始支持對(duì) Kafka 和 Rabbit 能力的進(jìn)行封裝,初步能夠完全支撐業(yè)務(wù)團(tuán)隊(duì)使用。
鑒于在之前已經(jīng)基本完全實(shí)施 Kafka 管控平臺(tái)、以及 Kafka 集群遷移管控,我們基本可以認(rèn)為團(tuán)隊(duì)對(duì)于 Kafka 的把控能力初具規(guī)模。
因此,考慮到以下幾點(diǎn)原因,我們決定對(duì) RabbitMQ 不再做維護(hù)和支持。
原因
使用混亂和維護(hù)困難
基于我們的數(shù)據(jù)統(tǒng)計(jì)和分析發(fā)現(xiàn),基本上沒有服務(wù)使用我們自己封裝的 RabbitMQ 能力,用到的基本上是??spring-amqp?
?或者原生的Rabbit 使用方式,存在使用混亂,方式不統(tǒng)一的問題,對(duì)于排查問題方面存在更多的問題。
另外考慮到對(duì)于 MQ 能力支持要做雙份,Kafka 和 Rabbit 都要支持相同的功能,對(duì)于人力資源方面存在浪費(fèi),當(dāng)然也由于本身目前沒有對(duì) RabbitMQ 非常精通的同學(xué),所以對(duì)于維護(hù)能力這方面存在擔(dān)憂。
分區(qū)容錯(cuò)問題
RabbitMQ 集群對(duì)于網(wǎng)絡(luò)分區(qū)的容錯(cuò)性不高,根據(jù)調(diào)查發(fā)現(xiàn),系統(tǒng)中 RabbitMQ 高可用方案使用鏡像隊(duì)列,而當(dāng) RabbitMQ 出現(xiàn)網(wǎng)絡(luò)分區(qū)時(shí),不同分區(qū)里的節(jié)點(diǎn)會(huì)認(rèn)為不屬于自身所在分區(qū)的節(jié)點(diǎn)都已經(jīng)掛了,對(duì)于隊(duì)列、交換器、綁定的操作僅對(duì)當(dāng)前分區(qū)有效。
而且,如果原集群中配置了鏡像隊(duì)列,而這個(gè)鏡像隊(duì)列又牽涉兩個(gè)或者更多個(gè)網(wǎng)絡(luò)分區(qū)中的節(jié)點(diǎn)時(shí),每一個(gè)網(wǎng)絡(luò)分區(qū)中都會(huì)出現(xiàn)一個(gè) master 節(jié)點(diǎn),對(duì)于各個(gè)網(wǎng)絡(luò)分區(qū),此隊(duì)列都是相互獨(dú)立的。
在默認(rèn)的情況下,架構(gòu)本身存在腦裂的風(fēng)險(xiǎn),在 3.1 版本下是無法自動(dòng)恢復(fù)的,之后的版本才會(huì)自動(dòng)探測(cè)網(wǎng)絡(luò)分區(qū),人工介入存在數(shù)據(jù)丟失的風(fēng)險(xiǎn)。
性能瓶頸
鏡像隊(duì)列解決了 Rabbit 高可用的問題,但是并不能增加負(fù)載和性能,線上曾經(jīng)出現(xiàn)過 RabbitMQ 在高流量下的性能問題,就是因?yàn)殛?duì)列由單個(gè)節(jié)點(diǎn)承載流量,在高并發(fā)情況在集群中單個(gè)節(jié)點(diǎn)存在性能瓶頸。
即便我們目前大部分場(chǎng)景下 MQ 流量不高,但是一旦出現(xiàn)問題,將成為整個(gè)系統(tǒng)的性能瓶頸。
另外我們對(duì) Rabbit 做了一些性能方面的測(cè)試:
測(cè)試集群一共有 4 臺(tái)磁盤節(jié)點(diǎn),其中每臺(tái) 16 核,如果我們不做 Sharding,單隊(duì)列最高 TPS 在 5K 左右,如果是內(nèi)存節(jié)點(diǎn),官方可以給出的處理極限為 50K/s,如果做 Sharding,單隊(duì)列處理能力可以達(dá)到 10K/s。
上述結(jié)論都是以消息能夠被正??焖傧M(fèi)為前提,實(shí)際上在高流量或者大量消息積壓的情況會(huì)導(dǎo)致集群性能急劇下降。
運(yùn)維&管控
基于以上現(xiàn)有的問題和難點(diǎn),我們決定對(duì) Rabbit 進(jìn)行全量遷移至 Kafka,以便能在業(yè)務(wù)高速發(fā)展過程中能夠保障對(duì)于穩(wěn)定性、高可用、高性能方面的追求。
在方法論和理論體系層面,我們對(duì)業(yè)務(wù)生產(chǎn)有三板斧:可灰度、可監(jiān)控、可回滾。
同樣,對(duì)于消息中間件平臺(tái)運(yùn)維我們希望有三板斧:可運(yùn)維、可觀測(cè)、可管控,那么目前基于 Kafka 的集群管控和 Kafka Manager 的能力我們已經(jīng)基本做到了上述幾點(diǎn)。
- 1. 高可用:根據(jù)自身經(jīng)驗(yàn),Kafka 本身?yè)碛袠O高的平臺(tái)可用性
- 2. 高性能:Kafka 可支撐極高的 TPS,并且支持水平擴(kuò)展,可快速滿足業(yè)務(wù)的流量增長(zhǎng)需求
- 3. 功能支持:在原有兩個(gè) MQ 能力基礎(chǔ)上,基礎(chǔ)支持順序消息、延時(shí)消息、灰度消息、消息軌跡等
- 4. 運(yùn)維管控:基于 Kafka Manager 基礎(chǔ)上進(jìn)行二次開發(fā),豐富管控能力和運(yùn)維支撐能力,提供給開發(fā)、運(yùn)維、測(cè)試更好的使用體驗(yàn)和運(yùn)維能力。
模型對(duì)比
RabbitMQ
Exchange:生產(chǎn)者將消息發(fā)送到Exchange,由交換器將消息通過匹配Exchange Type、Binding Key、Routing Key后路由到一個(gè)或者多個(gè)隊(duì)列中。
Queue:用于存儲(chǔ)消息,消費(fèi)者直接綁定Queue進(jìn)行消費(fèi)消息
Routing Key:生產(chǎn)者發(fā)送消息給 Exchange 會(huì)指定一個(gè)Routing Key。
Binding Key:在綁定Exchange與Queue時(shí)會(huì)指定一個(gè)Binding Key。
Exchange Type:
- ? Direct:把消息路由到那些 Binding Key 和 Routing Key 完全匹配的隊(duì)列中
- ? Fanout:把消息轉(zhuǎn)發(fā)給所有與它綁定的隊(duì)列上,相當(dāng)于廣播模式
- ? Topic:通過對(duì)消息的 Routing Key 和 Exchange、Queue 進(jìn)行匹配,將消息路由給一個(gè)或多個(gè)隊(duì)列,發(fā)布/訂閱模式
- ? Headers:根據(jù)消息的 Header 將消息路由到不同的隊(duì)列,和 Routing Key 無關(guān)
Kafka
Topic:發(fā)送消息的主題,對(duì)消息的組織形式
Broker:Kafka 服務(wù)端
Consumer Group:消費(fèi)者組
Partition:分區(qū),topic 會(huì)由多個(gè)分區(qū)組成,通常每個(gè)分區(qū)的消息都是按照順序讀取的,不同的分區(qū)無法保證順序性,分區(qū)也就是我們常說的數(shù)據(jù)分片sharding機(jī)制,主要目的就是為了提高系統(tǒng)的伸縮能力,通過分區(qū),消息的讀寫可以負(fù)載均衡到多個(gè)不同的節(jié)點(diǎn)上
遷移方案
綜上,我們將要對(duì)系統(tǒng)中所有使用RabbitMQ的服務(wù)進(jìn)行遷移操作,整個(gè)遷移我們應(yīng)該保證以下 3 點(diǎn):
- 1. 操作便捷,不能過于復(fù)雜,復(fù)雜會(huì)帶來更多的不可控風(fēng)險(xiǎn)
- 2. 風(fēng)險(xiǎn)可控,盡最大可能降低遷移對(duì)業(yè)務(wù)的影響
- 3. 不影響業(yè)務(wù)正常運(yùn)行
消費(fèi)者雙訂閱
- 1. 對(duì)消費(fèi)者進(jìn)行改造,同時(shí)監(jiān)聽 Rabbit 和 Kafka 消息
- 2. 對(duì)生產(chǎn)者進(jìn)行改造,遷移至Kafka發(fā)送消息
- 3. 等待 Rabbit 遺留消息消費(fèi)完畢之后,直接下線即可
優(yōu)點(diǎn):可以做到無損遷移
缺點(diǎn):
- 1. 需要同時(shí)維護(hù)兩套監(jiān)聽代碼,可能有大量的工作量,遷移完成之后還需要再進(jìn)行一次老代碼下線
- 2. 消息無法保證順序性
基于灰度單訂閱
這是基于雙訂閱模式的優(yōu)化,通過使用我們的灰度/藍(lán)綠發(fā)布的能力,做到可以不雙訂閱,不用同時(shí)監(jiān)聽兩個(gè)消息隊(duì)列的消息。
- 1. 直接修改消費(fèi)者代碼,發(fā)布灰度/藍(lán)節(jié)點(diǎn),監(jiān)聽 Kafka 消息
- 2. 生產(chǎn)者改造,往 Kafka 發(fā)送消息
- 3. 等待老的 Rabbit 消息消費(fèi)完畢,下線,這里存在一個(gè)問題就是在進(jìn)行灰度之后全量的過程中可能造成消息丟失的情況,對(duì)于這個(gè)問題的解決方案要區(qū)分來看,如果業(yè)務(wù)允許少量的丟失,那么直接全量即可,否則需要對(duì)業(yè)務(wù)做一定的改造,比如增加開關(guān),全量之前關(guān)閉發(fā)送消息,等待存量消息消費(fèi)完畢之后再全量。
優(yōu)點(diǎn):
- 1. 基于雙訂閱方案改造,可以做到不同時(shí)監(jiān)聽兩個(gè)隊(duì)列的消息,減少工作量
- 2. 可以做到無損遷移
缺點(diǎn):同樣無法保證消息有序性
實(shí)際場(chǎng)景問題
上述只是針對(duì)現(xiàn)狀的遷移方案考慮,那么還有一些跟實(shí)際和復(fù)雜的問題可能需要考慮。
比如消息的場(chǎng)景有可能不是這種簡(jiǎn)單的發(fā)布/訂閱關(guān)系,可能存在網(wǎng)狀、環(huán)狀的發(fā)布/訂閱關(guān)系,該如何處理?
其實(shí)是一樣的道理,只要我們能夠梳理清楚每個(gè) Exchange 之間的發(fā)布/訂閱的關(guān)系,針對(duì)每個(gè) Exchange 進(jìn)行操作,就能達(dá)到一樣的平滑遷移效果。
我們要做的就是針對(duì)每個(gè) Exchange 進(jìn)行遷移,而不是針對(duì)服務(wù),否則遷移是無法進(jìn)行下去的,但是這樣帶來的另外一個(gè)問題就是每個(gè)服務(wù)需要發(fā)布多次,而且如果碰到多個(gè)復(fù)雜消費(fèi)或者生產(chǎn)的情況要特別小心。
實(shí)施細(xì)節(jié)
基于現(xiàn)狀,我們對(duì)所有 Rabbit Exchange 的情況進(jìn)行了詳細(xì)的統(tǒng)計(jì),將針對(duì)不同的 Exchange 和類型以及功能使用以下方式處理。
- 1. 無用的Exchange、無生產(chǎn)者或者無消費(fèi)者,還有沒有任何流量的,可以直接刪除
- 2. Fanout 類型,Exchange 對(duì)應(yīng) Topic,Queue 對(duì)應(yīng) Consumer Group,還有存在使用隨機(jī)隊(duì)列的,需要對(duì)應(yīng)多個(gè)Consumer Group(單獨(dú)做一個(gè)簡(jiǎn)單的能力封裝處理)
- 3. Direct 類型,RoutingKey 對(duì)應(yīng) Topic,Queue 對(duì)應(yīng) Consumer Group
- 4. Topic 類型,RoutingKey 對(duì)應(yīng) Topic,Queue 對(duì)應(yīng) Consumer Group,實(shí)際并未發(fā)現(xiàn)使用到通配符情況
- 5. 延遲隊(duì)列、重試等功能,基于 spring-kafka 做二次封裝
驗(yàn)證&監(jiān)控&灰度&回滾
驗(yàn)證
- 遷移后針對(duì) Rabbit 驗(yàn)證,通過管理平臺(tái)流量或者日志輸出來確認(rèn),而且現(xiàn)狀是大部分 Exchange 流量都比較小,所以可能需要自行發(fā)送消息驗(yàn)證遷移效果。
- 遷移后針對(duì) Kafka 流量進(jìn)行驗(yàn)證可以通過 Kafka Manager 平臺(tái)或者日志
監(jiān)控
監(jiān)控通過 Kafka Manager 平臺(tái)或者現(xiàn)有監(jiān)控
灰度
方案本身 Consumer 和 Producer 都可以直接灰度發(fā)布,預(yù)發(fā)驗(yàn)證
回滾
服務(wù)回滾,按照發(fā)布順序控制回退順序
巨人的肩膀:
1. https://xie.infoq.cn/article/bf3d9cfd01af72b326254aa81
2. https://developer.aliyun.com/article/772095
3. 《RabbitMQ實(shí)戰(zhàn)指南》