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