vivo 在 2016 年引入 RabbitMQ,基於開源 RabbitMQ 進行擴展,向業務提供消息中間件服務。java
2016~2018年,全部業務均使用一個集羣,隨着業務規模的增加,集羣負載愈來愈重,集羣故障頻發。spring
2019年,RabbitMQ 進入高可用建設階段,完成了高可用組件 MQ 名字服務以及 RabbitMQ 集羣的同城雙活建設。網絡
同時進行業務使用集羣的物理拆分,嚴格按照集羣負載狀況和業務流量進行業務使用集羣的分配以及動態調整。架構
在 2019 年高可用建設後至今,業務流量增長了十倍,集羣未出現過嚴重故障。this
RabbitMQ 是實現了 AMQP 協議的開源消息代理軟件,起源於金融系統。spa
具備豐富的特性:插件
- 消息可靠性保證,RabbitMQ 經過發送確認保證消息發送可靠、經過集羣化、消息持久化、鏡像隊列的方式保證消息在集羣的可靠、經過消費確認保證消息消費的可靠性。
- RabbitMQ 提供了多種語言的客戶端。
- 提供了多種類型的 exchange,消息發送到集羣后經過exchange路由到具體的queue中。
- RabbitMQ 提供了完善的管理後臺和管理 API,經過管理API能夠快速與自建監控系統整合。
RabbitMQ 在具體實踐中發現的問題:3d
- 爲保障業務高可用使用多套集羣進行物理隔離,多套集羣無統一平臺進行管理。
- 原生RabbitMQ客戶端使用集羣地址鏈接,使用多套集羣時業務須要關心集羣地址,使用混亂。
- 原生RabbitMQ僅有簡單的用戶名/密碼驗證,不對使用的業務應用方進行鑑權,不一樣業務容易混用exchange/queue信息,形成業務應用使用異常。
- 使用的業務應用方較多,無平臺維護消息發送方、消費方的關聯信息,多個版本迭代後沒法肯定對接方。
- 客戶端無限流,業務突發異常流量衝擊甚至擊垮集羣。
- 客戶端無異常消息重發策略,須要使用方實現。
- 集羣出現內存溢出等形成集羣阻塞時沒法快速自動轉移到其它可用集羣。
- 使用鏡像隊列,隊列的master節點會落在具體某個節點上,在集羣隊列數較多時,容易出現節點負載不均衡的狀況。
- RabbitMQ無隊列自動平衡能力,在隊列較多時容易出現集羣節點負載不均問題。
過往業務團隊適用RabbitMQ時,應用申請的流量以及對接的應用等信息都在線下表格記錄,較爲零散,更新不及時,沒法準確瞭解業務當前真實的使用狀況,所以經過一個接入申請的流程可視化、平臺化創建應用使用的元數據信息。代理
經過MQ-Portal的申請流程(如上圖),肯定了消息發送應用、消費應用、使用exchange/queue、發送流量等信息使用申請提交後將進入vivo內部工單流程進行審批。code
工單流程審批經過後,經過工單的接口回調,分配應用具體使用的集羣,並在集羣上建立exchange/queue已經綁定關係。
因爲採用多集羣物理隔離的方式保證業務在正式環境的高可用,沒法簡單經過一個exchange/queue的名稱定位到使用的集羣。
每個exchange/queue與集羣之間經過惟一的一對rmq.topic.key與rmq.secret.key進行關聯,這樣SDK啓動過程當中便可定位到具體使用的集羣。
rmq.topic.key與rmq.secret.key將在工單的回調接口中進行分配。
客戶端SDK基於spring-message和spring-rabbit進行封裝,並在此基礎上提供了應用使用鑑權、集羣尋址、客戶端限流、生產消費重置、阻塞轉移等能力。
開源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); }
前文說過,應用使用RabbitMQ嚴格按照集羣的負載狀況和業務流量進行集羣的分配,所以具體某個應用使用的的不一樣的exchange/queue多是分配在不一樣的集羣上的。
爲了提高業務的開發效率, 須要屏蔽多集羣對業務的影響,所以按照應用配置的rmq.topic.key信息進行集羣的自動尋址。
原生SDK客戶端不進行發送流量限流,在部分應用存在異常持續向MQ發送消息時,可能會沖垮MQ集羣。而且一個集羣爲多應用共同使用,單一應用形成集羣影響將會影響使用異常集羣的全部應用。
所以須要在SDK中提供客戶端限流的能力,必要時能夠限制應用向集羣發送消息,保障集羣的穩定。
(1)隨着業務規模增加,集羣負載持續增長,此時須要進行集羣的業務拆分。爲了減小在拆分過程當中避免業務重啓,須要有生產消費重置功能。
(2)集羣出現異常,可能會形成消費者掉線,此時經過生產消費重置能夠快速拉起業務消費。
爲了實現生產消費重置,須要實現一下流程:
- 重置鏈接工廠鏈接參數
- 重置鏈接
- 創建新的鏈接
- 從新啓動生產消費
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); connectionFactory.setAddresses(address); connectionFactory.resetConnection(); rabbitAdmin = new RabbitAdmin(connectionFactory); rabbitTemplate = new RabbitTemplate(connectionFactory);
同時MQ-SDK中有異常消息重發策略,能夠避免在生產重置過程當中致使的消息發送異常。
RabbitMQ在內存使用超過40%,或是磁盤使用超限制時會阻塞消息發送。
因爲vivo中間件團隊已經完成了RabbitMQ同城雙活的建設,所以在出現一個集羣發送阻塞時能夠經過生產消費重置到雙活集羣完成阻塞的快速轉移。
隨着應用的發展,單集羣將沒法知足應用的流量需求,而且集羣隊列均爲鏡像隊列,沒法簡單的經過增長集羣節點的方式實現業務支撐流量單集羣的水平擴容。
所以須要SDK支持多集羣調度能力,經過將流量分散到多個集羣上知足業務大流量需求。
MQ-NameServer爲無狀態服務,經過集羣部署便可保障自身高可用,主要用於解決如下問題:
- MQ-SDK啓動鑑權以及應用使用集羣定位。
- 處理MQ-SDK的定時指標上報(消息發送數量、消息消費數量),而且返回當前可用集羣地址,確保SDK在集羣異常時按照正確地址進行重連。
- 控制MQ-SDK進行生產消費重置。
RabbitMQ 集羣均採用同城雙活部署架構,依靠MQ-SDK和MQ-NameServer提供的集羣尋址、故障快速切換等能力保障集羣的可用性。
RabbitMQ官方提供了三種集羣腦裂恢復策略。
(1)ignore
忽略腦裂問題不處理,在出現腦裂時須要進行人爲干預纔可恢復。因爲須要人爲干預,可能會形成部分消息丟失,在網絡很是可靠的狀況可使用。
(2)pause_minority
節點在與超過半數集羣節點失聯時將會自動暫停,直到檢測到與集羣超半數節點的通訊恢復。極端狀況下集羣內全部節點均暫停,形成集羣不可用。
(3)autoheal
少數派節點將自動重啓,此策略主要用於優先保證服務的可用性,而不是數據的可靠性,由於重啓節點上的消息會丟失。
因爲RabbitMQ集羣均爲同城雙活部署,即便單集羣異常業務流量也可自動遷移到雙活機房集羣,所以選擇使用了pause_minority策略避免腦裂問題。
2018年屢次因網絡抖動形成集羣腦裂,在修改集羣腦裂恢復策略後,已未再出現腦裂問題。
RabbitMQ採用集羣化部署,而且由於集羣腦裂恢復策略採用pause_minority模式,每一個集羣要求至少3個節點。
推薦使用5或7節點部署高可用集羣,而且控制集羣隊列數量。
集羣隊列均爲鏡像隊列,確保消息存在備份,避免節點異常致使消息丟失。
exchange、queue、消息均設置爲持久化,避免節點異常重啓消息丟失。
隊列均設置爲lazy queues,減小節點內存使用的波動。
雙機房部署等價集羣,而且經過Federation插件將雙集羣組成聯盟集羣。
本機房應用機器優先鏈接本機房MQ集羣,避免因專線抖動形成應用使用異常。
經過MQ-NameServer心跳獲取最新的可用集羣信息,異常時重連到雙活集羣中,實現應用功能的快速恢復。
目前對RabbitMQ的使用加強主要在MQ-SDK和MQ-NameServer側,SDK實現較爲複雜,後期但願能夠構建消息中間件的代理層,能夠簡化SDK而且對業務流量作更加細緻化的管理。
做者:derek