作者:vivo 網際網路中介軟體團隊 - Liu Tao
在開源 RocketMQ 基礎之上,關於【測試環境專案多版本隔離】業務訴求的落地與實踐。
一、背景
在2022年8月份 vivo 網際網路中介軟體團隊完成了網際網路線上業務的MQ引擎升級,從RabbitMQ 到 RocketMQ 的平滑升級替換。
在業務使用訊息中介軟體的過程中,提出了開發測試環境專案多版本隔離的訴求。本文將介紹我們基於 RocketMQ 如何實現的多版本環境隔離。
二、訊息中介軟體平臺主體架構
在正式展開專案多版本實踐之前,先大致介紹下我們訊息中介軟體平臺的主體架構。
由上圖可知,我們訊息中介軟體平臺的核心元件 mq-meta、RabbitMQ-SDK、mq-proxy,以及RocketMQ叢集。
1. mq-meta
主要負責平臺後設資料管理,以及業務SDK啟動時的鑑權定址操作。
業務進行topic申請時,會自動分配建立到兩個不同機房的broker上。
鑑權定址時會根據業務接入Key找到所在 MQ 叢集下的proxy節點列表,經過機房優先+分片選取+負載均衡等策略,下發業務對應的proxy節點列表。
2. RabbitMQ-SDK
目前業務使用的訊息中介軟體SDK仍為原有自研的RabibitMQ SDK,透過AMQP協議收發訊息。
與proxy之間的生產消費連線,遵循機房優先原則,同時亦可以人為指定優先機房策略。
3. mq-proxy
訊息閘道器元件,負責AMQP協議與RocketMQ Remoting協議之間的相互轉換,對於業務側目前僅開放了AMQP協議。
具備讀寫分離能力,可配置只代理生產、只代理消費、代理生產消費這三種角色。
與broker之間的生產消費,遵循機房優先原則。
機房優先的實現:
-
生產:proxy優先將訊息傳送到自己本機房的broker,只有在傳送失敗降級時,才會將訊息傳送到其他機房broker;透過擴充套件MQFaultStrategy+LatencyFaultTolerance,並結合快手負載均衡元件simple-failover-java實現機房優先+機房級別容災的負載均衡策略。
-
消費:在進行佇列分配時,先輪詢分配自己機房的佇列;再將不存在任何消費的機房佇列,進行輪詢分配。透過擴充套件AllocateMessageQueueStrategy實現。
4. RocketMQ叢集
每個MQ叢集會由多個機房的broker組成。
每個topic則至少會分配到兩個不同機房的broker上。實現業務訊息傳送與消費的機房級別的容災。
每個broker部署兩節點,採用主從架構部署,並基於zookeeper實現了一套自動主從切換的高可用機制。透過非同步刷盤+同步雙寫來保證效能與訊息的可靠性。
namesrv則為跨機房broker+mq-proxy之間的公共元件,為叢集提供路由發現功能。
三、專案多版本實踐
3.1 現狀
後端服務通常採用微服務架構,各服務之間的通訊,通常是同步與非同步兩種呼叫場景。其中同步是透過RPC呼叫完成,而非同步則是透過MQ(RocketMQ)生產消費訊息實現。
在多版本環境隔離中,同步呼叫場景,一些RPC框架都能有比較好的支援(如Dubbo的標籤路由);但在非同步呼叫場景,RocketMQ並不具備完整的版本隔離方案,需要透過組合一些功能自行實現。
最初訊息中介軟體平臺支援的多版本環境隔離大致如下:
-
平臺提供固定幾個MQ邏輯叢集(測試01、測試02、測試03...)來支援版本隔離。
-
業務在進行多版本的並行測試時,需關注版本環境與MQ邏輯叢集的對應關係,一個版本對應到一個MQ邏輯叢集。
-
不同MQ邏輯叢集下用到的MQ資源(Topic、Group)自然就是不同的。
該方式主要存在如下兩個問題:
1、使用成本較高
-
業務需在訊息中介軟體平臺進行多套環境(叢集)的資源申請。
-
業務在部署多版本時,每個版本服務都需要配置一份不同的MQ資源接入Key,配置過程繁瑣且容易出錯。
2、環境維護成本較高
-
在一個專案中,業務為了測試完整的業務流程,可能會涉及到多個生產方、消費方服務。儘管在某次版本中只改動了生產方服務,但仍需要在版本環境中一併部署業務流程所需的生產與消費方服務,增加了機器與人力資源成本。
為解決上述問題,提升多版本開發測試過程中的研發效率,中介軟體團隊開始了RocketMQ多版本環境隔離方案的調研。
3.2 方案調研
註釋:
1、物理隔離:即機器層面的隔離,MQ的物理隔離,則意味著使用完全不同的MQ物理叢集。
2、資源邏輯隔離:屬於同一MQ物理叢集,但採用不同的邏輯叢集,業務側需關注不同邏輯叢集下相應的topic和group資源配置。
3、基線版本:通常為當前線上環境的版本或者是當前的主開發版本,為穩定版本。
4、專案版本:即專案並行開發中的多版本,非基線版本。
5、訊息回落:針對消費而言,若消費方沒有對應的專案版本,則會回落到基線版本來進行消費。
3.3 方案選擇
基於我們需解決的問題,並對實現成本與業務使用成本的綜合考量,我們僅考慮【基於訊息維度的user-property】與【基於topic的messageQueue】這兩種方案。
又因在全鏈路的多版本環境隔離的需求中,業務使用的版本環境明確提出不做固定,故而我們最終選擇【基於訊息維度的user-property】來作為我們多版本環境隔離的方案。
3.4 專案多版本的落地
基於訊息維度的user-property來實現專案多版本的隔離。
1. 鏈路分析
在多版本環境中,真實的業務鏈路可能如下,服務呼叫可能走同步RPC或非同步MQ。
註釋:
1、業務請求中帶有流量標識,經過閘道器時,根據流量路由規則將流量染色為全鏈路染色標識v-traffic-lane。
2、流量標識為userId,流量路由規則為使用者路由到指定版本,圖中的鏈路情況:
3、在後續的整個鏈路中,都需要將請求按照流量染色標識v-traffic-lane正確路由到對應版本環境。
2. 染色標識傳遞
為了正確識別當前服務所在版本,以及流量中的染色標識進行全鏈路傳遞,需要做如下事情:
(1)啟動
其中v-traffic-lane則是服務被拉起時所在的版本環境標識(由CICD提供),這樣proxy就能知道這個客戶端連線屬於哪個版本。
(2)訊息的傳送與接收
訊息傳送:mq-proxy將AMQP訊息轉化為RocketMQ訊息時,將染色標識新增到RocketMQ訊息的user-property中。
訊息接收:mq-proxy將RocketMQ訊息轉化為AMQP訊息時,將染色標識再新增到AMQP訊息屬性中。
註釋:
上述紅色點位,可透過改動SDK進行染色標識的傳遞,但這樣就需要業務升級SDK了。這裡我們是藉助呼叫鏈agent來統一實現。
3.生產消費邏輯
(1)生產
邏輯比較簡單,對於存在版本tag的訊息,只需要將版本標識作為一個訊息屬性,儲存到當前topic中即可。
(2)消費
這裡其實是有兩個問題:消費的多版本隔離、訊息回落。
我們先看下消費的多版本隔離應該如何實現?
透過使用不同的消費group,採用基於user-property的訊息過濾機制來實現。
① 版本tag傳遞
-
在RabbitMQ-SDK消費啟動時,透過全鏈路Agent傳遞到proxy
② 專案環境消費【消費屬於自己版本的訊息】
-
proxy會根據版本tag在MQ叢集自動建立帶版本tag的group,並透過消費訂閱的訊息屬性過濾機制,只消費自己版本的訊息。
-
routingKey的過濾則依賴proxy側的過濾來完成。相對基線版本,多版本的訊息量應該會比較少,全量拉取到proxy來做過濾,影響可控。
-
消費組group_版本tag無需業務申請,由客戶端啟動時proxy會自動建立。
③ 基線消費【消費全部基線版本訊息+不線上多版本的訊息】
-
啟動時使用原始group,訂閱消費時,基於broker的routingKey過濾機制消費topic所有訊息。
-
當訊息被拉取到proxy後,再做一次訊息屬性過濾,將多版本進行選擇性過濾,讓基線消費到正確版本的訊息。
我們再來看下訊息回落又該如何實現?
1、訊息回落是基線消費需要根據多版本的線上情況,來決定是否需要消費多版本的訊息。
2、上面已提到基線消費從broker是拉取所有訊息進行消費。
3、我們透過在基線消費內部維護一個線上多版本tag的集合,然後進行多版本訊息的選擇性過濾來支援回落。
4、但這個線上多版本tag的集合,需要及時更新,才能更好的保證訊息回落的準確性。
5、起初我們採用定時任務從broker拉取所有線上多版本tag的集合,每30s拉取一次,這樣訊息回落就需要30s才能生效,準確性差。
6、後面我們想到用廣播通知機制,在多版本上下線時廣播通知到所有的基線消費例項,保證了訊息回落的實效性與準確性。
7、完整的基線消費例項線上多版本tag集合更新機制如下:
(3)broker側的調整
這裡主要是為了配合消費多版本的實現,對broker進行了一些擴充套件。
1、提供線上多版本group集合的擴充套件介面。用以返回當前group所有線上的多版本group集合。
2、增加broker側多版本訊息過濾機制。因RocketMQ原生sql92過濾表示式,無法支援帶點的屬性欄位過濾;而我們的版本標識(_vh_.v-traffic-lane)是存在的。
註釋:
1、routingKey過濾機制:為基於broker的訊息過濾機制的擴充套件,可實現RabbitMQ中的routingKey表示式相同的訊息路由功能。
2、多版本生產消費邏輯,都在mq-proxy與RocketMQ-broker側完成。業務也無需升級SDK。
4. 問題定位
在多版本隔離中,平臺對使用者遮蔽了複雜的實現細節,但使用者使用時,也需要能觀測到訊息的生產消費情況,便於問題跟蹤定位。
這裡我們主要提供瞭如下功能:
① 訊息查詢:可觀測訊息當前的版本標識,以及訊息軌跡中的生產消費情況
② 消費group的線上節點:可看到消費節點當前的版本標識
四、總結與展望
本文概述了vivo網際網路中介軟體團隊,在開源RocketMQ基礎之上,如何落地【測試環境專案多版本隔離】的業務訴求。其中涵蓋了vivo訊息中介軟體主體架構現狀、業內較流行的幾種方案對比,並對我們最終選擇方案在實現層面進行了細節性的分析。希望可以給業界提供一種基於proxy來實現多版本隔離特性的案例參考。
在實現過程中遇到的問題點歸結下來則是:
1. 流量染色標識在整個生產消費過程中如何傳遞?
-
在客戶端SDK使用全鏈路agent進行流量染色標識的新增、拆解、傳遞。
-
在RocketMQ則儲存到訊息的user-property當中。
2. 消費客戶端版本標識如何識別?
-
客戶端SDK使用全鏈路agent將版本標識新增到連線屬性當中。
-
proxy則根據客戶端版本標識自動建立多版本消費group。
3. 消費的多版本隔離如何實現?
-
專案版本,透過不同的消費group,基於broker端訊息屬性的版本過濾來實現隔離。
-
基線版本,則透過proxy側消費過濾來忽略掉不需要消費的訊息。
4. 訊息回落如何實現?如何保證訊息回落的實效性與準確性?
-
基線版本內部會維護一個線上多版本消費group的集合,根據這個集合來決定訊息是否需要回落到基線進行消費。
-
訊息回落的實效性與準確性則透過定時+廣播訊息的機制保證。
最後,我們實現的多版本隔離特性如下:
-
多版本環境隔離。在proxy層面基於訊息維度user-property來實現版本隔離,業務不需要升級SDK,業務使用層面仍然為同一套配置資源。
-
支援訊息回落。
-
消費失敗產生的重試訊息也能被重投遞到對應版本。
但仍存在如下不足:
多版本消費客戶端全部下線場景:若topic中仍存在一些已下線版本的訊息沒有消費,則這部分訊息不保證一定能被基線版本全部消費到。因基線版本與專案版本實際上採用的是不同的消費group,在broker的消費進度是不一致的,訊息回落到基線消費之後,其消費位點可能已經超過專案版本消費group下線時的位點,中間存在偏差,會導致這部分訊息再無法被基線版本消費到。
建議用於開發測試環境,因其無法保證多版本訊息至少會被消費一次。
未來,訊息中介軟體也會考慮線上環境全鏈路灰度場景的支援。
附錄:
-
RocketMQ 全鏈路灰度探索與實踐 + 配置訊息灰度
-
快手 RocketMQ 高效能實踐 + simple-failover-java
-
平安銀行在開源技術選型上的思考和實踐
-
vivo 魯班平臺 RocketMQ 訊息灰度方案
-
OpenSergo
-
從RabbitMQ平滑遷移到RocketMQ技術實戰