這樣做RabbitMQ高可用,業務流量猛增10倍也不慫
一、背景說明
vivo 在 2016 年引入 RabbitMQ,基于開源 RabbitMQ 進行擴展,向業務提供消息中間件服務。
2016~2018年,所有業務均使用一個集群,隨著業務規模的增長,集群負載越來越重,集群故障頻發。
2019年,RabbitMQ 進入高可用建設階段,完成了高可用組件 MQ 名字服務以及 RabbitMQ 集群的同城雙活建設。
同時進行業務使用集群的物理拆分,嚴格按照集群負載情況和業務流量進行業務使用集群的分配以及動態調整。
在 2019 年高可用建設后至今,業務流量增加了十倍,集群未出現過嚴重故障。
RabbitMQ 是實現了 AMQP 協議的開源消息代理軟件,起源于金融系統。
具有豐富的特性:
- 消息可靠性保證,RabbitMQ 通過發送確認保證消息發送可靠、通過集群化、消息持久化、鏡像隊列的方式保證消息在集群的可靠、通過消費確認保證消息消費的可靠性;
- RabbitMQ 提供了多種語言的客戶端;
- 提供了多種類型的 exchange,消息發送到集群后通過exchange路由到具體的queue中;
- RabbitMQ 提供了完善的管理后臺和管理 API,通過管理API可以快速與自建監控系統整合。
RabbitMQ 在具體實踐中發現的問題:
- 為保障業務高可用使用多套集群進行物理隔離,多套集群無統一平臺進行管理;
- 原生RabbitMQ客戶端使用集群地址連接,使用多套集群時業務需要關心集群地址,使用混亂;
- 原生RabbitMQ僅有簡單的用戶名/密碼驗證,不對使用的業務應用方進行鑒權,不同業務容易混用exchange/queue信息,造成業務應用使用異常;
- 使用的業務應用方較多,無平臺維護消息發送方、消費方的關聯信息,多個版本迭代后無法確定對接方;
- 客戶端無限流,業務突發異常流量沖擊甚至擊垮集群;
- 客戶端無異常消息重發策略,需要使用方實現;
- 集群出現內存溢出等造成集群阻塞時無法快速自動轉移到其它可用集群;
- 使用鏡像隊列,隊列的master節點會落在具體某個節點上,在集群隊列數較多時,容易出現節點負載不均衡的情況;
- RabbitMQ無隊列自動平衡能力,在隊列較多時容易出現集群節點負載不均問題。
二、整體架構
1、MQ-Portal--支持應用使用申請
過往業務團隊適用RabbitMQ時,應用申請的流量以及對接的應用等信息都在線下表格記錄,較為零散,更新不及時,無法準確了解業務當前真實的使用情況,因此通過一個接入申請的流程可視化、平臺化建立應用使用的元數據信息。
通過MQ-Portal的申請流程(如上圖),確定了消息發送應用、消費應用、使用exchange/queue、發送流量等信息使用申請提交后將進入vivo內部工單流程進行審批。
工單流程審批通過后,通過工單的接口回調,分配應用具體使用的集群,并在集群上創建exchange/queue已經綁定關系。
由于采用多集群物理隔離的方式保證業務在正式環境的高可用,無法簡單通過一個exchange/queue的名稱定位到使用的集群。
每一個exchange/queue與集群之間通過唯一的一對rmq.topic.key與rmq.secret.key進行關聯,這樣SDK啟動過程中即可定位到具體使用的集群。
rmq.topic.key與rmq.secret.key將在工單的回調接口中進行分配。
2、客戶端SDK能力概述
客戶端SDK基于spring-message和spring-rabbit進行封裝,并在此基礎上提供了應用使用鑒權、集群尋址、客戶端限流、生產消費重置、阻塞轉移等能力。
1)應用使用鑒權
開源RabbitMQ僅通過用戶名密碼的方式判斷是否允許連接集群,但是應用是否允許使用exchange/queue是未進行校驗的。
為了避免不同業務混用exchange/queue,需要對應用進行使用鑒權。
應用鑒權由SDK和MQ-NameServer協同完成。
應用啟動時首先會上報應用配置的rmq.topic.key信息到MQ-NameServer,由MQ-NameServer判斷使用應用與申請應用是否一致,并且在SDK發送消息過程中還會進行二次校驗。
- /**
- * 發送前校驗,并且獲取真正的發送factory,這樣業務可以聲明多個,
- * 但是用其中一個bean就可以發送所有的消息,并且不會導致任何異常
- * @param exchange 校驗參數
- * @return 發送工廠
- */
- public AbstractMessageProducerFactory beforeSend(String exchange) {
- if(closed || stopped){
- //上下文已經關閉拋出異常,阻止繼續發送,減少發送臨界狀態數據
- throw new RmqRuntimeException(String.format("producer sending message to exchange %s has closed, can't send message", this.getExchange()));
- }
- if (exchange.equals(this.exchange)){
- return this;
- }
- if (!VIVO_RMQ_AUTH.isAuth(exchange)){
- throw new VivoRmqUnAuthException(String.format("發送topic校驗異常,請勿向無權限exchange %s 發送數據,發送失敗", exchange));
- }
- //獲取真正的發送的bean,避免發送錯誤
- return PRODUCERS.get(exchange);
- }
2)集群尋址
前文說過,應用使用RabbitMQ嚴格按照集群的負載情況和業務流量進行集群的分配,因此具體某個應用使用的的不同的exchange/queue可能是分配在不同的集群上的。
為了提升業務的開發效率, 需要屏蔽多集群對業務的影響,因此按照應用配置的rmq.topic.key信息進行集群的自動尋址。
3)客戶端限流
原生SDK客戶端不進行發送流量限流,在部分應用存在異常持續向MQ發送消息時,可能會沖垮MQ集群。并且一個集群為多應用共同使用,單一應用造成集群影響將會影響使用異常集群的所有應用。
因此需要在SDK中提供客戶端限流的能力,必要時可以限制應用向集群發送消息,保障集群的穩定。
4)生產消費重置
①隨著業務規模增長,集群負載持續增加,此時需要進行集群的業務拆分。為了減少在拆分過程中避免業務重啟,需要有生產消費重置功能。
②集群出現異常,可能會造成消費者掉線,此時通過生產消費重置可以快速拉起業務消費。
為了實現生產消費重置,需要實現一下流程:
- 重置連接工廠連接參數;
- 重置連接;
- 建立新的連接;
- 重新啟動生產消費。
- CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
- connectionFactory.setAddresses(address);
- connectionFactory.resetConnection();
- rabbitAdmin = new RabbitAdmin(connectionFactory);
- rabbitTemplate = new RabbitTemplate(connectionFactory);
同時MQ-SDK中有異常消息重發策略,可以避免在生產重置過程中導致的消息發送異常。
5)阻塞轉移
RabbitMQ在內存使用超過40%,或是磁盤使用超限制時會阻塞消息發送。
由于vivo中間件團隊已經完成了RabbitMQ同城雙活的建設,因此在出現一個集群發送阻塞時可以通過生產消費重置到雙活集群完成阻塞的快速轉移。
6)多集群調度
隨著應用的發展,單集群將無法滿足應用的流量需求,并且集群隊列均為鏡像隊列,無法簡單的通過增加集群節點的方式實現業務支撐流量單集群的水平擴容。
因此需要SDK支持多集群調度能力,通過將流量分散到多個集群上滿足業務大流量需求。
3、MQ-NameServer--支持MQ-SDK實現故障快速切換
MQ-NameServer為無狀態服務,通過集群部署即可保障自身高可用,主要用于解決以下問題:
- MQ-SDK啟動鑒權以及應用使用集群定位;
- 處理MQ-SDK的定時指標上報(消息發送數量、消息消費數量),并且返回當前可用集群地址,確保SDK在集群異常時按照正確地址進行重連;
- 控制MQ-SDK進行生產消費重置。
4、MQ-Server高可用部署實踐
RabbitMQ 集群均采用同城雙活部署架構,依靠MQ-SDK和MQ-NameServer提供的集群尋址、故障快速切換等能力保障集群的可用性。
1)集群腦裂問題處理
RabbitMQ官方提供了三種集群腦裂恢復策略。
①ignore
忽略腦裂問題不處理,在出現腦裂時需要進行人為干預才可恢復。由于需要人為干預,可能會造成部分消息丟失,在網絡非常可靠的情況可以使用。
②pause_minority
節點在與超過半數集群節點失聯時將會自動暫停,直到檢測到與集群超半數節點的通信恢復。極端情況下集群內所有節點均暫停,造成集群不可用。
③autoheal
少數派節點將自動重啟,此策略主要用于優先保證服務的可用性,而不是數據的可靠性,因為重啟節點上的消息會丟失。
由于RabbitMQ集群均為同城雙活部署,即使單集群異常業務流量也可自動遷移到雙活機房集群,因此選擇使用了pause_minority策略避免腦裂問題。
2018年多次因網絡抖動造成集群腦裂,在修改集群腦裂恢復策略后,已未再出現腦裂問題。
2)集群高可用方案
RabbitMQ采用集群化部署,并且因為集群腦裂恢復策略采用pause_minority模式,每個集群要求至少3個節點。
推薦使用5或7節點部署高可用集群,并且控制集群隊列數量。
集群隊列均為鏡像隊列,確保消息存在備份,避免節點異常導致消息丟失。
exchange、queue、消息均設置為持久化,避免節點異常重啟消息丟失。
隊列均設置為lazy queues,減少節點內存使用的波動。
3)同城雙活建設
雙機房部署等價集群,并且通過Federation插件將雙集群組成聯盟集群。
本機房應用機器優先連接本機房MQ集群,避免因專線抖動造成應用使用異常。
通過MQ-NameServer心跳獲取最新的可用集群信息,異常時重連到雙活集群中,實現應用功能的快速恢復。
三、未來挑戰與展望
目前對RabbitMQ的使用增強主要在MQ-SDK和MQ-NameServer側,SDK實現較為復雜,后期希望可以構建消息中間件的代理層,可以簡化SDK并且對業務流量做更加細致化的管理。