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