本文作者:蔣星韜,網易雲音樂服務端開發工程師。
雲音樂線上場景眾多,比如直播、評論、廣告,各個業務線都會有訊息場景比如發獎券,也會有延遲訊息和事務訊息場景,以及大資料做埋點資料、資料清洗、離線處理等。
雲音樂線上 RocketMQ topic 為 1 萬+/天,QPS 流量峰值為150萬/s,日訊息量千億級別。為了支撐龐大的資料規模和場景,除了搭建開源RocketMQ叢集,我們也做了監控的完善和工具體驗。監控完善主要包括對整個叢集的容量、狀態、水位進行健康狀態的監控,針對訊息的傳送和消費提供流量、延遲、失敗、耗時等監控指標。基於以上監控指標,還需搭建一套業務巡檢體系,以實現線上告警。
另外,我們也提供改了一些工具幫助業務方提升使用 RocketMQ 的體驗,比如資料遷移和同步訊息路由的元件,提供穩定性保障的限流能力、降級能力以及動態引數干預的預案能力。當線上業務方發現消費不符合預期時,需要提供查詢幫助其快速定位,以及提供死信處理工具等。
雲音樂目前有三個機房,每個機房部署了一套 RocketMQ 叢集,除了 Manesrv、 HA 等基礎元件,還有自研或開源改造的元件,比如 monitor 元件、告警巡檢元件、降級維穩元件等。
每個機房裡有一套平臺化的管控元件,管控端包含提工單、上下線、查資料、訂閱問題,還包括一套訊息路平臺和資料庫。
網易雲音樂擁有多個流量入口,不同業務的資料和流量需要做隔離,每個租戶下都是一套獨立的業務線。而物理隔離成本過高,因此我們實現了邏輯隔離。各個業務之間流量不互通,邏輯上無法相互呼叫,且租戶下所有 topic 名字一致,中臺只需要切換租戶名,無需改動任何其配置、程式碼,即可直接上線。
所有 topic 都在一個物理叢集內,每個租戶有自己的一套邏輯叢集,邏輯叢集內有自己的 topic,不同邏輯叢集之間的 topic 同名,實現了多租戶隔離。
隨著雲音樂的業務愈發龐大,業務方提出了更多需求。比如異地多活,訊息需要在多個機房消費,比如通用埋點資料,需要將多個產品的資料彙總到機場的資料處理叢集做離線處理,比如架構升級,不同單元間的流量能夠動態排程。
基於以上需求,訊息路由需要實現以下幾個功能:
①跨機房訊息複製。
②流量去重:訊息路由在複製時不可避免會有失敗,因此必然有內部的重試,可能會導致有訊息重複;此外,雙向路由必然需要提供雙向複製,而兩邊 topic 名字一樣,複製時會導致錯亂,因此需要有標籤來實現流量重。
③資料遷移任務。
④監控完善,進度可控。
雲音樂的訊息路由實現方案如上圖所示。
首先,在管控平臺會維護一套路由任務後設資料表,業務方可以提工單或者透過其他方式申請路由任務,支援任意機房的任意兩個 topic 之間做訊息路由。任務提交之後,訊息路由叢集會定時同步管控端上的訊息路由任務的狀態,同時將訊息傳送到目標 topic 。路由任務能夠自行上報監控資料、消費延遲、堆積監控報表等,可在管控端進行檢視。
雲音樂的資料處理任務包括埋點、trace,大多使用Flink。但由於開源方向沒有與我們的需求非常匹配的 connector ,因此我們封裝實現了自己的 RocketMQ Flink connector。
因為內部封裝了介面和叢集配置,RocketMQ 作為 Flink 的 source 和 sink 需要有資料來源的配置。我們對資料來源做了封裝,比如 connector 如何解析後設資料,從而正確地連線資料來源、讀寫訊息。
大資料任務的特點為測試環境與線上資料會混在一起,多環境都有接入需求,因此我們設計了一套後設資料,使得 connect 能夠連線多環境且能夠處理多環境裡面流量標、環境標等標籤的過濾。
Flink有自己的 checkpoint 機制,只有在做 checkpoint 時才會將 consumer offset 提交給 broker ,同時需要對 consumer offset 進行管理,否則消費位點消失會導致資料重新消費,因此我們實現了 state 管理機制。
Flink的 spot task 比較敏感,丟擲錯誤則會導致 task 重新執行,連續重複幾次後會導致TaskManager failover 。此外,RocketMQ 在網路場景下時常出現broker busy 或網路問題導致傳送失敗異常。我們針對Flink 定製了一套異常場景處理,使其變得不敏感。
此外,我們目前面臨的線上問題主要包括訊息流量激增、機器負載高、大資料任務突刺、重置消費位點等。叢集突然出現大流量行動時,其穩定性會受到極大衝擊,頻繁傳送失敗,線上其業務也會受到 topic 的影響。
面對以上問題,除了提供隔離能力外,也需要限流降級的能力。
第一,服務端的傳送限流。支援 topic 級別,也支援 group 級別。後續將支援客戶端級別,支援多個維度的傳送端限流。
第二,全域性消費限流。分為 topic 和 group,可以對整個 group 消費關係下所有機器的總量進行限流,適用於大資料場景。
第三,單機消費限流。適用於線業務場景,因為線上業務場景每臺機器的負載有限,不希望某個業務無上限地佔用資源,因此需要對單機限流。線上業務叢集容量不夠時,可以做動態擴容來增加容量。增加叢集容量時無需修改全域性容量。
上圖折線圖反應了開啟單機限流之後,消費資料隨著釋出緩慢平穩上漲,解決了流量突刺,提升線上叢集穩定性和消費服務的負載平穩。
隨著叢集規模增大,逐漸出現了訊息延遲的情況。經排查發現,producer 能夠正常傳送訊息到 broker ,但是由於資料量非常大,後臺建立 consumer queue 的速度跟不上傳送速度,導致消費延遲。其次,消費也面臨瓶頸,跟不上傳送速度,因為同一個傳送可能存在多個消費方。
針對以上問題,我們進行了索引最佳化。
開源版本下, commit log 寫入之後,會有 Reput service 方法建 consumer queue 、index 索引等一套流程,從頭掃到尾,塊狀地建立 consumer queue。
而我們發現,保證順序性和位點的有序性的前提下,可以併發地建索引,只需處理好位點的提交即可。因此,我們設定了 reput queue 非同步執行緒池,裡面有不同的 reput task ,每個 task 建立自己的 comment log 索引。建好之後,索引並不是立刻可見。建好之後會有全域性的索引往後推,如果前面的 commit log 索引已經建好,則後面的索引也立馬可見,提升了索引的建立效率。
上圖為索引最佳化前後的效能對比。橫軸代表 topic 數量,縱軸代表建索引的速度。灰色線代表建索引的速度,橙色線代表傳送速度。
最佳化前,topic 較少時,建索引的速度慢於傳送速度。隨著 topic 數量增多,兩者速度逐漸一致,但效能均明顯下降。
最佳化後,建索引的速度基本與傳送速度持平,且效能不會隨著 topic 數量增加而大幅下降。最佳化後建索引的效能達到最佳化前的3倍,保證了消費效能。
廣播消費場景下,通常需要確保所有機器上的資料最終一致。而開源的廣播消費失敗後不會重試也不會告警。且消費位點為 local,不會上報到遠端,如果本地服務重啟則offset 丟失,並且無法做預案干預。同時因為不上報,缺少問題定位的能力。
我們的解決方案為邏輯 group +實際 group 。
邏輯 group 指業務方在程式碼和在管控平臺申請的 consumer group 。申請 group 之後,在客戶端進行設定,將其標識為新版廣播消費的 group,每個例項啟動時在邏輯group 後加上副檔名來生成實際 group 。
實際 group 可以進行正常的叢集消費,也可以用複用叢集消費的所有能力,包括租戶隔離、訊息路由、監控、限流能力等,最終就解決了廣播消費的問題,能夠使用死信、重試、重置消費位點、位點查詢、監控告警等能力。
此外,雲音樂日常還會出現線上流量突增來不及釋出,或線上流量與預期不符而釋出成本過高等問題。
為此,我們為業務方提供了實時執行緒式調整的能力。開源版本中,每個客戶端都會向broker 做 Consumer RunningInfo 的上報,包括是否消費暫停、subscribe 的狀態、訂閱了哪些 topic、消費位點等。我們在在上報資訊里加上了每個 topic 自己消費的執行緒池的 coresize、maxsize ,並在管控端展示,使使用者能夠實時感知當前執行緒池的狀態。
此外,我們提供了非常簡單的修改方式。在管控端修改 kv config 並上報到 NameSvr ,NameSvr 監聽並下發。客戶端監聽到 kv config 變化後將最新配置下拉。然後再本地找到 topic 對應的執行緒池,修改 coresize、maxsize 值。
此前,業務線上上發現問題後釋出往往需要 10-20 分鐘起步。而現在只需修改一個引數、下發、輪詢即可完成,整個過程不超過 30 秒。
雲音樂在 RocketMQ 的未來規劃如下:
第一,雲原生。雲原生有彈性擴縮容的能力,可以更好地節約成本以及應對線上突發風險。
第二,提效率。比如 Topic 籤遷移、從一個叢集遷移到另一個叢集、從順序訊息改為非順序訊息等操作目前還未實現完全白屏化,後續會針對此方面提高效率,提高使用者體驗。
第三,開源社群交流貢獻。