馬蜂窩消息總線於 2017 年 11 月份上線,截至目前,已經被電商、酒店、大交通、社區等多個技術團隊投入到生產環境的使用中。後端
近一年時間裏,消息總線經歷過幾回比較重要的功能迭代,承擔了 PHP 在線服務異步、削峯、解耦的大部分任務。安全
這篇文章的目的主要是和你們交流下馬蜂窩消息總線的設計緣由、實現原理以及將來規劃,但願能和有潛在需求的研發同窗一塊兒探討。微信
在消息總線上線前,馬蜂窩大部分業務中的異步需求是經過 Redis 隊列來實現。隨着消息量增長,常常會出現消息積壓、不一樣消息之間互相影響的問題。爲解決這些問題,電商研發團隊開始規劃和設計消息總線。架構
爲何會有消息總線,而不是讓業務系統直接用 PHP 或者其餘語言對接 RabbitMQ,Kafka 這樣的消息系統?併發
「消息總線和直接使用消息系列有什麼實際的區別?」,這是不少研發同窗一開始不太理解的地方。假如只是爲了用一個性能更好的消息系統代替 Redis,確實並不須要消息總線的這個角色。異步
但當咱們從實際業務角度出發,對公司總體技術架構和開發場景的梳理時,發現若是直接讓業務系統對接消息系統,並非一個很好的方式,而且至少會面臨如下問題:微服務
整體來講,直接使用消息系統能夠被當作是一個面向技術的接入方式;而消息總線則指望經過隱藏部署、分組和通訊等細節,實現一個面向業務的接入方式。性能
消息總線隱藏了消息發送、路由、分組、存儲、消費負載、通訊、高可用等一些列問題。對使用者來講,只須要在發送端調用一個 SDK 消息發送方法,在消費端提供一個 PHP 消費方法便可。優化
圖1 馬蜂窩消息總線架構設計spa
馬蜂窩消息總線當前使用 RabbitMQ 做爲消息引擎,在發送端提供了 SDK,做爲消息總線的 Broker 角色,包含了消息路由分組的功能,負責消息的 Publish。
消息的訂閱關係,目前是持久化在 MySQL 中,在消息發送時會根據訂閱關係把消息投遞到對應的業務消費者。
而在消費端,並無直接用 PHP 去接入 RabbitMQ,而是使用 Deliver 服務集羣 (Golang 服務) 來負責把 AMQP 協議轉爲 HTTP 協議,而後經過 PHPService 進行消費 PHP 代碼的執行。
這個方案在設計時,同時考慮到了將來系統規模擴展後的消息分組,以及關鍵環節的可替代性。
SDK 充當了消息服務 Broker 的角色,能夠控制消息的路由、分組。將來在微服務體系中能夠保持總體架構不變,只採用其餘方案實現 Broker。
能夠根據業務場景對接不一樣的消息引擎,好比對業務一致性要求高的業務使用 RabbitMQ,而對併發要求較高的可使用 Kafka。對業務來講是無感知的。
Deliver 和 Application Service 之間可擴展更多的通訊協議,支持應用更靈活的消費方式,包括支持將來在微服務中的消費服務。
爲了保證消息在消息總線內各環節流轉時減小複雜度,可以被統一處理,消息體被設計爲統一的結構。主要分爲如下 3 個部分:
圖2 消息體的定義
點對點模式是業務中經常使用的一種異步模式,
圖3 點對點消息模式
業務應用把不須要在同步請求中執行的邏輯放到異步去執行。發送消息的業務須要明確處理消息的接收者 (消費的 PHP 方法)。消息在發送時須要明確指定惟一的一個 Receiver。
當前經過消息總線 SDK 提供的 invoke 方法能夠指定消費的應用方法。
圖4 發佈訂閱(廣播)
App 1 的應用只負責發出消息,至於什麼業務須要關注,下游業務應用本身訂閱該消息就能夠。很大程度上減小了上游業務和下游業務的耦合程度和開發調試成本。
消息總線使用 DB 來進行消息訂閱關係的存儲,上游業務的消息通過消息總線 Broker 時會根據訂閱關係,裂變爲 Receiver 是訂閱應用的多條消息。這樣的消息裂變方式使消息後續在消息總線流轉時目標明確,在進行消費負載,消費確認,失敗重試等場景時能夠按照 Receiver 進行隔離。
一樣調用方可使用 SDK 提供的 pub 方法進行消息的發送,訂閱方經過消息管理系統進行消息訂閱的申請。
不少使用消息總線的同窗比較關心不一樣消息之間是否會相互干擾,好比因爲某個消息短期內大量涌入是否會形成其餘消息被阻塞。
經過前面架構的介紹,能夠看到全部的消息通過 Broker 時能夠進行路由、分組。消息總線將來會根據業務和消息量來作一些物理隔離,保障業務之間不會相互影響。
而在一個分組內,消息總線也有一些機制保障分組內的不一樣消息不會相互影響。
圖5 防消息干擾機制
消息通過 Broker 默認會進入一個 Online Queue 的隊列中,Deliver 集羣中會有多個 Deliver 監聽 Online Queue。在 Deliver 服務內,經過 Dispatcher 來控制消息總併發消費量,以及同類型消息的併發消費量。當某種類型的併發消息數量超過閾值時,就會被轉發到 Offline Queue,避免消費 Worker 都被同一個類型的消息佔用。而 Offline Queue 會被獨立的 Deliver 服務監聽進行消費,不影響 Online Queue 的消費。
爲了保證消費時的高可用,Deliever 羣在負責進行消費協議轉換以外,也作了一些策略來保證消費端的高可用。
◆ 熔斷
在消息一段時間內失敗數量超過閾值時,中止對隊列的消費,避免因爲服務抖動和線上故障引發的大面積消息。
◆ 消費失敗
熔斷後,Deliver 服務會對後端應用服務健康度進行監控,在服務恢復後可自動恢復消費。
◆ 系統失敗重試
消息總線服務發生故障時,可對期間的失敗消息採用重試策略進行重試,避免因爲基礎服務問題形成的消費失敗。
◆ 業務失敗重試
在業務應用消費時產生業務異常,可在訂閱消息時指定是否進行重試。消息總線會對須要失敗的消息按照必定的時間週期進行屢次重試。
◆ Graceful 重啓
Deliver 實現了 Graceful 重啓和退出,保障當前正在消費的消息都處理完成後纔會進程退出。
圖7 將來演進方向
當前消息總線在功能上通過近一年的迭代,已經基本穩定。但在消息管理,監控,統計等環節對開發者來講還不夠友好,接下來一段時間會着重優化系統的易用性。
目前已經在規劃如下方向的優化改進:
開發者能夠經過消息管理系統進行新增消息,訂閱消息 (加入權限的審覈) 等操做,代替當前手工提 issue 的方式。
開發者能夠經過系統關注到本身消息的消費狀況,並及時接收到消息處理異常的報警。
完善監控體系,提供更精細維度的系統監控數據。
關於在微服務架構內提供消息總線服務,也已經在計劃當中。包括在微服務內進行消息發送和使用某個微服務進行消息的消費。將來整個消息總線計劃會往下圖的架構進行演進,增長對多語言和不一樣架構服務的支持。適應更多的業務開發場景,提供更穩定,友好的消息總線服務。
另外對消息引擎的技術選型,將來也會考慮接入 Kafka,RocketMQ 等其餘消息隊列服務。根據不一樣業務場景的消息特性,在發佈時選擇進入不一樣的消息隊列服務。好比對可靠性,數據安全性要求高的消息會進入 RabbitMQ,而對高吞吐量的消息能夠進入 Kafka。但對消息的發送方和訂閱方來講均可以不用關心這些細節,仍然按照統一的方式進行接入。
馬蜂窩消息總線服務當前也在不斷迭代中,在不少地方還有很多沒有考慮到的問題。歡迎你們多提寶貴意見,您能夠掃描下方二維碼訂閱「馬蜂窩技術」更多內容。
本文做者:梁亮,馬蜂窩電商研發團隊技術專家。
微信關注「馬蜂窩技術」公衆號,閱讀更多技術乾貨。