
阿里妹導讀
本文將從概念、設計、實現和適用場景等多個維度介紹Redis Stream在交通模組的應用。
一、背景
交通路況團隊主要負責AMAP(高德地圖)軌跡收容和實時計算,不僅承擔了即時路況的計算和釋出,而且利用海量使用者軌跡,陸續推出紅綠倒計時和V2X(道路預警)等重磅功能,作為海量資料即時計算的基礎鏈路,自然少不了對訊息中介軟體的使用,一直以來我們都是集團MQ團隊的重要客戶,隨著業務的快速發展,資料規模和計算頻次進一步提升,MQ成本急劇增加,替換更加合適的訊息中介軟體成為了必然。
目前集團內部廣泛使用的成熟的訊息中介軟體有MQ、TT等,這些訊息中介軟體通常具備高可用,高吞吐,低延遲等特點,同時具備相對完善的控制檯和專業團隊的支援,但是從成本的角度出發,現有的訊息中介軟體並非合適的選擇,經過前期調研,我們最終確定使用Redis Stream作為新的替換方案,截至目前,交通鏈路的主要環節已經完成了由MQ到Redis Stream的升級,並且取得了顯著的成本和延時收益。接下來將從概念、設計、實現和適用場景等多個維度介紹Redis Stream在交通模組的應用。
二、Redis Stream概念
Redis Stream是Redis 5.0版本新增加的資料結構,主要用於訊息佇列。關於Redis Stream的具體細節,可以參照官網,閱讀本篇文章中只需要理解以下概念即可。

圖2.1 redis stream結構
-
Redis Stream資料結構的value是一個FIFO的佇列,可以透過redis命令指定佇列長度,當訊息超出佇列長度時會自動將最早的訊息刪除,出於效能的考慮,Redis Stream提供了惰性刪除的選項,惰性刪除不會在每次新增訊息時嚴格地刪除多餘的訊息,而是透過週期性、閾值觸發等機制來刪除舊訊息;
-
Redis Stream中的每一條訊息由id和content構成,id可以手動指定,預設規格是“UNIX時間戳_序號”,時間戳是訊息在redis記憶體建立時的ms時間戳,序號用於區分同一時刻上的不同訊息,content即為儲存的訊息體;
-
Redis Stream支援多個消費者組重複消費訊息(廣播消費),同一個消費者組下可以建立若干個消費者,多個消費者共同消費同一份資料(叢集消費);
-
Redis Stream為每一個消費者組記錄了消費位點 last_delivered_id;
-
Redis Stream同時提供了ACK機制,用於消費訊息的確認。
三、設計與實現
在交通使用MQ的場景中,上游應用透過雜湊規則計算資料的tag,往同一個topic寫資料,下游應用的每臺機器消費固定的tag,保證同一類資料在同一臺機器上進行處理,本文基於這種生產消費模式介紹Redis Stream SDK(C++)的實現。
在現有版本的Redis Stream SDK中,生產者和消費者只需指定topic和tag等簡單資訊,就可以實現訊息的生產和消費,無需關心實現細節,Redis Stream SDK支援多例項、執行緒配置、同步非同步模式、消費位點重置、負載均衡、即時監控,斷網重連等功能。
3.1負載均衡
在使用Redis Stream作為訊息中介軟體時,我們需要考慮兩個問題,一個是Redis Stream沒有tag的概念,另一個是redis例項包含多個分片,同時使用多個例項的情況下,如何保證均勻的使用每個分片,防止資料傾斜。
3.1.1 topic拆分

圖3.1 拆分示例
在MQ中,同一個topic下可以有若干個tag,每次傳送需要攜帶topic和tag資訊,消費者可以指定tag進行消費,這樣既實現叢集消費,又保證了同類資料被同一下游處理,而在Redis Stream中沒有tag的概念,只有topic的概念,準確的說,只有key的概念,一個topic即為一個key,訊息的佇列即為key對應的value,為了保證原有功能不變,我們將topic進行拆分,生產者和消費者指定的topic實際上僅為topic的字首,真正在redis記憶體中儲存的topic(redis的key)實際上是topic和tag的完整資訊,形式為“topic_tag”,上游傳送訊息指定topic和tag,SDK計算出完整的topic並將訊息寫入,下游消費指定topic和tag,SDK計算出完整的topic進行拉取,這樣便實現了“tag”的功能。
3.1.2 分片雜湊
在上一部分中,我們解決了tag的問題,接下來的問題是在已知topic和tag的前提下,如何確定訊息需要被髮送到哪個例項的那個分片,以及如何保證訊息被寫入到目標分片。

圖3.2 雜湊說明
以圖3.2為例,圖中共有4個32分片redis例項,每個例項都有例項idx,每個分片都有區域性分片idx和全域性分片idx,那麼可以透過下列方式計算例項和分片資訊。
全域性idx = tag % 分片總數
例項idx = 全域性idx / 單例項分片數
區域性idx = 全域性idx % 單例項分片數
redis叢集作為一個分散式系統,整個資料庫空間會被分為16384個槽(slot),每個資料分片節點將儲存與處理指定slot的資料,例如3分片叢集例項,3個分片分別負責的slot為:[0,5460]、[5461,10922]、[10923,16383],redis透過CRC演算法計算出key所屬的slot,進而確定key所屬的分片,當key中包含{}字串,redis僅會根據{}中的值計算slot,我們可以透過遍歷的方式暴力計算得到所有slot的雜湊字串並進行儲存,確定區域性分片idx後可以直接查詢,因此,完整的redis stream的topic格式為“topic_tag_{分片雜湊字串}”。
3.2跨機房讀寫
在使用訊息中介軟體時,跨機房讀寫是不可避免的,對於跨機房讀寫的場景,在開發過程中對比了兩種跨機房方案,一個是使用hiredis非同步模式,另一種是使用集團redis提供的全球多活。

圖3.3 跨機房部署
如圖3.3所示,生產者部署在na610機房,消費者部署在su121機房,在非同步方案中,訊息生產採用非同步模式,訊息消費採用同步模式,在全球多活方案中,訊息生產和消費均採用同步模式,在保證資料規模相同,且讀寫執行緒足夠的情況下,非同步模式的平均延遲在22~23ms,全球多活的平均延遲在51ms~57ms,非同步模式延遲明顯小於全球多活,除此之外,全球多活方案需要額外申請redis 例項,需要更多的redis資源。
3.3工程實現
現有的SDK版本支援靈活的配置,支援使用多個redis例項,可變的消費/生產/處理執行緒,主要配置如下:
-
生產者
-
例項資訊:使用的redis例項資訊,支援多個redis例項; -
單個例項傳送執行緒數:多個執行緒間遵循輪詢的規則,保證每個執行緒負載均衡;
-
消費者
-
例項資訊:使用的redis例項資訊,支援多個redis例項; -
tag資訊:訂閱的tag列表,用於初始化消費執行緒; -
單個執行緒消費tag數量上限:redis stream支援單次從若干個tag拉取資料(redis限制:tag必須在同一分片),如果單次拉取tag數量過大,會導致消費積壓; -
處理執行緒數量:單個消費執行緒對應的處理執行緒數量,處理執行緒呼叫註冊的回撥函式,當回撥函式比較耗時,需要配置較多的處理執行緒。

圖3.4 Redis Stream流程圖
3.4即時監控
集團現有的訊息中介軟體通常具有完善的監控能力和告警機制,可以即時檢視和監測訊息鏈路的異常。集團redis例項叢集本身提供了CPU、記憶體、頻寬等諸多維度的監控,但是對於消費延遲/積壓,卻沒有現成的支援,因此,使用Redis Stream作為中介軟體時使用以下多個指標來綜合監控訊息延遲/積壓:
-
生產消費訊息量級:在沒有積壓的情況下,生產者和消費者的訊息量級大致是相同的。
-
單次拉取數量:目前消費者只採用了同步消費的模式,在單次拉取訊息時,需要指定單次拉取的最大訊息數量,當出現積壓時,拉取數量會持續接近最大閾值。
-
延時統計:透過對單次寫入讀取延遲的監控,可以監控由網路問題可能造成的訊息積壓。
3.5壓測表現

圖3.5 單執行緒生產

圖3.6 單執行緒消費
線上環境同步模式下,單執行緒生產消費TPS上限隨著訊息大小的增加而減小,訊息10k以下TPS上限為3000以上,訊息增加到100k時,TPS上限降低為1500。
四、實踐經驗
4.1線上表現
目前交通鏈路各個環節MQ升級為Redis Stream已經基本完成,已持續穩定執行一段時間,並取得了顯著的成本收益和時延收益,相較於MQ,成本和時延均下降90%+。以某一環節為例,該環節高峰期間訊息量級約2000w/min,平均訊息大小1k,使用4個 64G 64分片 redis例項,叢集日常水位如下:

圖4.1 redis叢集水位
4.2適用場景
MQ
|
TT
|
Redis Stream
|
|
優點
|
1.專業的訊息中介軟體產品,功能強大,具備訊息不丟、訊息重試、延遲訊息、叢集/廣播訂閱等特性
2.高可用,高吞吐,低延遲
3.完善的控制檯能力,具備報表、報警和訊息追蹤/驗證等特性
|
1.專業的訊息中介軟體產品,除訊息重試、延遲訊息外與MQ功能基本一致
2.具備更豐富的生產者能力,如sdk、日誌採集、binlog同步等
3.高可用,高吞吐,低延遲
4.完善的控制檯能力,具備報表、報警等能力
|
|
缺點
|
1.費用高,讀寫均收費
|
1.費用高,讀收費
2.彈內缺乏C++客戶端
|
1.redis持久化問題,服務端異常時,佇列資料可能丟失;
2.缺乏訊息中介軟體運維平臺
|
總的來說,Redis Stream適用於訊息量級較大成本較高的業務,但需要接受可能的訊息丟失,由於redis記憶體有限,不支援儲存大量訊息,所以通常不支援有追資料需求的業務,除此之外,使用Redis Stream作為中介軟體需要投入運維資源,需要提前合理預估好資源用量,必要時需要進行手動擴縮容。
4.3踩過的坑/經驗分享
-
C++建議使用hiredis的最新版本(1.2.0),最新版本非同步模式支援設定連線超時時間,方便非同步連線成功的判斷;
-
使用Redis Stream作為中介軟體,訊息不宜過大(100k以下),否則訊息過大,單執行緒讀寫TPS會有明顯的下降;
-
在大量資料的業務場景中,tag的數量不宜太少,大量資料通常要使用較多的redis資源,如果tag數量太少,容易導致資料傾斜甚至某些分片無法利用;
-
redis例項資源預估主要考慮的因素是CPU,而不是記憶體和頻寬,Redis Stream可以靈活的設定佇列長度,記憶體通常是可控的, CPU與訊息數量有關,具體相關性需要具體實踐。
一鍵訓練大模型及部署GPU共享推理服務
透過建立ACK叢集Pro版,使用雲原生AI套件提交模型微調訓練任務與部署GPU共享推理服務。
點選閱讀原文檢視詳情。