1. 程式人生 > >vivo 基於原生 RabbitMQ 的高可用架構實踐

vivo 基於原生 RabbitMQ 的高可用架構實踐

一、背景說明

vivo 在 2016 年引入 RabbitMQ,基於開源 RabbitMQ 進行擴充套件,向業務提供訊息中介軟體服務。

2016~2018年,所有業務均使用一個叢集,隨著業務規模的增長,叢集負載越來越重,叢集故障頻發。

2019年,RabbitMQ 進入高可用建設階段,完成了高可用元件 MQ 名字服務以及 RabbitMQ 叢集的同城雙活建設。

同時進行業務使用叢集的物理拆分,嚴格按照叢集負載情況和業務流量進行業務使用叢集的分配以及動態調整。

在 2019 年高可用建設後至今,業務流量增加了十倍,叢集未出現過嚴重故障。

RabbitMQ 是實現了 AMQP 協議的開源訊息代理軟體,起源於金融系統。

具有豐富的特性:

  1. 訊息可靠性保證,RabbitMQ 通過傳送確認保證訊息傳送可靠、通過叢集化、訊息持久化、映象佇列的方式保證訊息在叢集的可靠、通過消費確認保證訊息消費的可靠性。
  2. RabbitMQ 提供了多種語言的客戶端。
  3. 提供了多種型別的 exchange,訊息傳送到集群后通過exchange路由到具體的queue中。
  4. RabbitMQ 提供了完善的管理後臺和管理 API,通過管理API可以快速與自建監控系統整合。

RabbitMQ 在具體實踐中發現的問題:

  1. 為保障業務高可用使用多套叢集進行物理隔離,多套叢集無統一平臺進行管理
  2. 原生RabbitMQ客戶端使用叢集地址連線,使用多套叢集時業務需要關心叢集地址,使用混亂。
  3. 原生RabbitMQ僅有簡單的使用者名稱/密碼驗證,不對使用的業務應用方進行鑑權,不同業務容易混用exchange/queue資訊,造成業務應用使用異常。
  4. 使用的業務應用方較多,無平臺維護訊息傳送方、消費方的關聯資訊,多個版本迭代後無法確定對接方。
  5. 客戶端無限流,業務突發異常流量衝擊甚至擊垮叢集。
  6. 客戶端無異常訊息重發策略,需要使用方實現。
  7. 叢集出現記憶體溢位等造成叢集阻塞時無法快速自動轉移到其它可用叢集。
  8. 使用映象佇列,佇列的master節點會落在具體某個節點上,在叢集佇列數較多時,容易出現節點負載不均衡的情況。
  9. 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進行封裝,並在此基礎上提供了應用使用鑑權、叢集定址、客戶端限流、生產消費重置、阻塞轉移等能力。

2.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.2、叢集定址

前文說過,應用使用RabbitMQ嚴格按照叢集的負載情況和業務流量進行叢集的分配,因此具體某個應用使用的的不同的exchange/queue可能是分配在不同的叢集上的。

為了提升業務的開發效率, 需要遮蔽多叢集對業務的影響,因此按照應用配置的rmq.topic.key資訊進行叢集的自動定址。

2.3、客戶端限流

原生SDK客戶端不進行傳送流量限流,在部分應用存在異常持續向MQ傳送訊息時,可能會沖垮MQ叢集。並且一個叢集為多應用共同使用,單一應用造成叢集影響將會影響使用異常叢集的所有應用。

因此需要在SDK中提供客戶端限流的能力,必要時可以限制應用向叢集傳送訊息,保障叢集的穩定。

2.4、生產消費重置

(1)隨著業務規模增長,叢集負載持續增加,此時需要進行叢集的業務拆分。為了減少在拆分過程中避免業務重啟,需要有生產消費重置功能。

(2)叢集出現異常,可能會造成消費者掉線,此時通過生產消費重置可以快速拉起業務消費。

為了實現生產消費重置,需要實現一下流程:

  • 重置連線工廠連線引數
  • 重置連線
  • 建立新的連線
  • 重新啟動生產消費
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setAddresses(address);
connectionFactory.resetConnection();
rabbitAdmin = new RabbitAdmin(connectionFactory);
rabbitTemplate = new RabbitTemplate(connectionFactory); 

同時MQ-SDK中有異常訊息重發策略,可以避免在生產重置過程中導致的訊息傳送異常。

2.5、阻塞轉移

RabbitMQ在記憶體使用超過40%,或是磁碟使用超限制時會阻塞訊息傳送。

由於vivo中介軟體團隊已經完成了RabbitMQ同城雙活的建設,因此在出現一個叢集傳送阻塞時可以通過生產消費重置到雙活叢集完成阻塞的快速轉移。

2.6、多叢集排程

隨著應用的發展,單叢集將無法滿足應用的流量需求,並且叢集佇列均為映象佇列,無法簡單的通過增加叢集節點的方式實現業務支撐流量單叢集的水平擴容。

因此需要SDK支援多叢集排程能力,通過將流量分散到多個叢集上滿足業務大流量需求。

3、MQ-NameServer--支援MQ-SDK實現故障快速切換

MQ-NameServer為無狀態服務,通過叢集部署即可保障自身高可用,主要用於解決以下問題:

  • MQ-SDK啟動鑑權以及應用使用叢集定位。
  • 處理MQ-SDK的定時指標上報(訊息傳送數量、訊息消費數量),並且返回當前可用叢集地址,確保SDK在叢集異常時按照正確地址進行重連。
  • 控制MQ-SDK進行生產消費重置。

4、MQ-Server高可用部署實踐

RabbitMQ 叢集均採用同城雙活部署架構,依靠MQ-SDK和MQ-NameServer提供的叢集定址、故障快速切換等能力保障叢集的可用性。

4.1、叢集腦裂問題處理

RabbitMQ官方提供了三種叢集腦裂恢復策略。

(1)ignore

忽略腦裂問題不處理,在出現腦裂時需要進行人為干預才可恢復。由於需要人為干預,可能會造成部分訊息丟失,在網路非常可靠的情況可以使用。

(2)pause_minority

節點在與超過半數叢集節點失聯時將會自動暫停,直到檢測到與叢集超半數節點的通訊恢復。極端情況下叢集內所有節點均暫停,造成叢集不可用。

(3)autoheal

少數派節點將自動重啟,此策略主要用於優先保證服務的可用性,而不是資料的可靠性,因為重啟節點上的訊息會丟失。

由於RabbitMQ叢集均為同城雙活部署,即使單叢集異常業務流量也可自動遷移到雙活機房叢集,因此選擇使用了pause_minority策略避免腦裂問題。

2018年多次因網路抖動造成叢集腦裂,在修改叢集腦裂恢復策略後,已未再出現腦裂問題。

4.2、叢集高可用方案

RabbitMQ採用叢集化部署,並且因為叢集腦裂恢復策略採用pause_minority模式,每個叢集要求至少3個節點。

推薦使用5或7節點部署高可用叢集,並且控制叢集佇列數量。

叢集佇列均為映象佇列,確保訊息存在備份,避免節點異常導致訊息丟失。

exchange、queue、訊息均設定為持久化,避免節點異常重啟訊息丟失。

佇列均設定為lazy queues,減少節點記憶體使用的波動。

4.3、同城雙活建設

雙機房部署等價叢集,並且通過Federation外掛將雙叢集組成聯盟叢集。

本機房應用機器優先連線本機房MQ叢集,避免因專線抖動造成應用使用異常。

通過MQ-NameServer心跳獲取最新的可用叢集資訊,異常時重連到雙活叢集中,實現應用功能的快速恢復。

三、未來挑戰與展望

目前對RabbitMQ的使用增強主要在MQ-SDK和MQ-NameServer側,SDK實現較為複雜,後期希望可以構建訊息中介軟體的代理層,可以簡化SDK並且對業務流量做更加細緻化的管理。

作者:derek