談談消息總線跟消息隊列的區別,以及對于企業級應用需要將消息隊列封裝成消息總線的必要性。
消息總線跟消息隊列有何區別?如果有人問你這個問題,你的答案是什么?如果你的消息總線是基于一個已經相當成熟的消息隊列或者消息系統做二次封裝。為什么需要用你的客戶端,而不直接用原始的(這是一個大家都相信權威的時代,請注意這里用的是相信,而不是迷信,你確實應該相信權威,至少比相信一個新手來得靠譜,當然我這里指的權威,是正面的意思)?
那么我從以下幾點來談談我對這個問題的思考:
消息隊列clientAPI權限太大,clientAPI信任級別太高
消息隊列clientAPI面向技術,消息總線clientAPI面向技術+業務
消息隊列無法隱藏通信細節
消息隊列無法實施實時管控
總線的優勢:統一入口,簡化攔截成本
這里為了理解簡單,你就暫且先把RabbitMQ當做是個消息隊列,其實它不只是個消息隊列,其他的一些基于JMS的消息隊列對于回答這個問題而言,也能成立。
(1)消息隊列clientAPI權限太大,信任級別太高
這一點不僅僅是哪一個服務端組件的客戶端driver的實現是這樣,絕大部分其實都是這樣的,它們的client其實是對服務端組件(或者稱之為服務器)協議的翻譯。這些服務器大都帶有commandline interface(這幾乎是標配)。其實,CLI跟在程序中使用的各種語言的client庫沒有區別本質區別,它們相對于server而言都是client——都是對server實現的protocol的翻譯或者轉換,而這些API都是對這些包裝過的協議的調用。因此它們都存在一些“management”形式的接口:比如create,delete,remove某個component之類的。沒錯,你去看所有帶client的組件的實現,它們都包含了這些API(這不是對錯的問題,這些client本身就沒有也不應該假設你的使用場景)。比如你看看redis的client:jedis——它甚至具備了flushAll,flushDB的功能(清空所有redis數據),除了能關閉server還有什么事它不能做?而就RabbitMQ而言,它的officialnative java client,可以創建/刪除其通信的核心組件:exchange,queue。你能直接將這些client散布到各個業務系統里去而不加阻攔?你當然有必要做二次封裝以移除這些高危的managementAPI。
(2)消息隊列clientAPI面向技術而消息總線clientAPI面向技術+業務
消息隊列的clientAPI大都面向協議、通信實現,面向可用性以及高性能,如果歸類一下那就是面向技術,除了通信場景它不會去模擬業務場景。而消息總線需要帶著業務場景去實現需要支持的機制。
當你去搜索任何一個消息隊列的時候,它的advantage里都有一條:生產者與消費者解耦,就像下面這樣:
就生產者跟消費者模型而言,這確實是消息隊列的優勢。不過這種優勢也被限制在一些特定的使用場景下,比如:單一業務的消息排隊處理。因此通用消息隊列的場景更適用于單一職責的生產者跟消費者模型;而我們期待的消息總線卻是企業里各個系統中消息的通信,側重點在于通信上。消息隊列只是提供了一種非常適合于消息通信的實現機制(消息有序,消息緩存等),因此消息總線是在消息隊列提供的技術支撐上封裝出適合消息交互的業務場景。
(3)消息隊列無法隱藏通信細節
對于企業內的系統交互,我們希望它盡可能保證數據的安全性。而數據通常都暫存在隊列中,因此保證數據的安全性就順其自然得轉變成保證消息隊列訪問的安全性:你總是不應該讓沒有經過授權的客戶端去訪問本不應該訪問到的隊列。可惜的是RabbitMQ官方的客戶端達不到這種要求,它要訪問一個隊列,需要知道真實隊列的名稱,需要知道其路由路徑。而就連接一個隊列而言,我們認為它提供了太多的信息,但這是沒辦法的事情,因為它的exchange以及queue的混搭機制非常靈活,所以你得提供一個稱之為routingkey的路由路徑。而不管怎樣,如果你把這個信息開放給調用端去填寫,幾乎肯定會暴露你服務端exchange以及queue的路由機制以及拓撲結構。因此我們需要做什么?我們需要找到一種通信機制,讓它對外只需要知道有個proxy節點,而不需要去關注真實的queue的名稱;然后想一個辦法把其routingkey隱藏在消息總線內部。
(4)消息隊列無法實施實時管控
如果你在企業內各個系統之間引入消息總線,很顯然訪問控制是必須提供的。比如對某個隊列實施消息大小限制,激活/禁用某個隊列等。
之前我們提到過消息隊列不是面向業務的,它自身沒有過多得考慮數據的安全性以及對訪問的安全控制機制。而且我們也幾乎很難去改造一個消息隊列的服務端實現,除非它是基于攔截器/插件模式的。即便RabbitMQ是支持插件的,但對于erlang這樣一個受眾不是特別廣泛的語言,你去給它寫插件一不小心就會走到坑里去,并且RabbitMQ官方也已經申明了它們十分不建議你自己去編寫插件。考慮到諸多不便,我們只能在客戶端上做文章。毫無疑問,我們的實時管控信息還是必須存儲在服務端(只是它是一個獨立的服務端),但原生的client很顯然是不支持這種機制的,因此我們需要在原生client外部封裝訂閱實時管控信息以及實施訪問控制的邏輯代碼。
(5)總線的優勢:統一入口,簡化攔截成本
無論是消息總線還是服務總線,其實所謂的總線就是進行先收攏再發散的過程。先收攏,從統一的入口進去,完成必要的統一處理邏輯;再發散,按照路由規則,路由到各個組件去處理。事實上這就是代理的作用:屏蔽內部細節,對外統一入口。在基于代理的基礎上,我們可以對消息總線上所有的消息做日志記錄(因為所有消息的通信都必須經過代理),并且還是在不切斷RabbitMQ自身Channel的基礎上,而如果想在路由上實現一個Proxy,那基本上離不開一個樹形拓撲結構。
寫在最后
這篇主要談了消息總線跟消息隊列的區別。其實市面上已經有一些成熟的消息隊列可以開箱即用,如果你針對消息隊列來封裝出一個消息總線,總有人會認為是否有這個必要性。如果沒有這些開源的消息隊列,那么完全有你自己來實現消息總線的話,你還是需要實現出一個跟市面上類似的MQ或者MessageBroker(見POSA卷4),因此消息隊列只是實現消息總線的基礎,或者是它的消息通信方式;而選擇基于一些成熟的MessageBroker來進行開發,既能省去很多的工作量,又能享有它們提供的穩定性以及社區的貢獻。
評論