目前主流的MQ產(chǎn)品

1.ZeroMQ
號(hào)稱最快的消息隊(duì)列系統(tǒng),尤其針對(duì)大吞吐量的需求場(chǎng)景。
擴(kuò)展性好,開(kāi)發(fā)比較靈活,采用C語(yǔ)言實(shí)現(xiàn),實(shí)際上只是一個(gè)socket庫(kù)的重新封裝,如果做為消息隊(duì)列使用,需要開(kāi)發(fā)大量的代碼。ZeroMQ僅提供非持久性的隊(duì)列,也就是說(shuō)如果down機(jī),數(shù)據(jù)將會(huì)丟失。其中,Twitter的Storm中使用ZeroMQ作為數(shù)據(jù)流的傳輸。
2.RabbitMQ
結(jié)合erlang語(yǔ)言本身的并發(fā)優(yōu)勢(shì),支持很多的協(xié)議:AMQP,XMPP, SMTP, STOMP,也正是如此,使的它變的非常重量級(jí),更適合于企業(yè)級(jí)的開(kāi)發(fā)。
性能較好,但是不利于做二次開(kāi)發(fā)和維護(hù)。
3.ActiveMQ
歷史悠久的開(kāi)源項(xiàng)目,是Apache下的一個(gè)子項(xiàng)目。已經(jīng)在很多產(chǎn)品中得到應(yīng)用,實(shí)現(xiàn)了JMS1.1規(guī)范,可以和spring-jms輕松融合,實(shí)現(xiàn)了多種協(xié)議,不夠輕巧(源代碼比RocketMQ多),支持持久化到數(shù)據(jù)庫(kù),對(duì)隊(duì)列數(shù)較多的情況支持不好。
4.Redis
做為一個(gè)基于內(nèi)存的K-V數(shù)據(jù)庫(kù),其提供了消息訂閱的服務(wù),可以當(dāng)作MQ來(lái)使用,目前應(yīng)用案例較少,且不方便擴(kuò)展。對(duì)于RabbitMQ和Redis的入隊(duì)和出隊(duì)操作,各執(zhí)行100萬(wàn)次,每10萬(wàn)次記錄一次執(zhí)行時(shí)間。
測(cè)試數(shù)據(jù)分為 128Bytes、512Bytes、1K和10K四個(gè)不同大小的數(shù)據(jù)。

實(shí)驗(yàn)表明:入隊(duì)時(shí),當(dāng)數(shù)據(jù)比較小時(shí)Redis的性能要高于RabbitMQ,而如 果數(shù)據(jù)大小超過(guò)了10K,Redis則慢的無(wú)法忍受;出隊(duì)時(shí),無(wú)論數(shù)據(jù)大小,Redis都表現(xiàn)出非常好的性能,而RabbitMQ的出隊(duì)性能則遠(yuǎn)低于 Redis。
5.Kafka/Jafka
Kafka是Apache下的一個(gè)子項(xiàng)目,是一個(gè)高性能跨語(yǔ)言分布式發(fā)布/訂閱消息隊(duì)列系統(tǒng),而Jafka是在Kafka之上孵化而來(lái)的,即Kafka的一個(gè)升級(jí)版。
具有以下特性:
- 快速持久化,可以在O(1)的系統(tǒng)開(kāi)銷下進(jìn)行消息持久化;
- 高吞吐,在一臺(tái)普通的服務(wù)器上既可以達(dá)到10W/s的吞吐速率;完全的分布式系統(tǒng),Broker、Producer、Consumer都原生自動(dòng)支持分布式,自動(dòng)實(shí)現(xiàn)負(fù)載均衡;
- 支持Hadoop數(shù)據(jù)并行加載,對(duì)于像Hadoop的一樣的日志數(shù)據(jù)和離線分析系統(tǒng),但又要求實(shí)時(shí)處理的限制,這是一個(gè)可行的解決方案。
- Kafka通過(guò)Hadoop的并行加載機(jī)制統(tǒng)一了在線和離線的消息處理。Apache Kafka相對(duì)于ActiveMQ是一個(gè)非常輕量級(jí)的消息系統(tǒng),除了性能非常好之外,還是一個(gè)工作良好的分布式系統(tǒng)。

何時(shí)需要消息隊(duì)列
當(dāng)你需要使用消息隊(duì)列時(shí),首先需要考慮它的必要性。
可以使用mq的場(chǎng)景有很多,最常用的幾種:
- 做業(yè)務(wù)解耦
- 最終一致性
- 廣播
- 錯(cuò)峰流控等
反之,如果需要強(qiáng)一致性,關(guān)注業(yè)務(wù)邏輯的處理結(jié)果,則RPC顯得更為合適。
消息隊(duì)列使用場(chǎng)景

1.解耦
解耦是消息隊(duì)列要解決的最本質(zhì)問(wèn)題。所謂解耦,簡(jiǎn)單點(diǎn)講就是一個(gè)事務(wù),只關(guān)心核心的流程。而需要依賴其他系統(tǒng)但不那么重要的事情,有通知即可,無(wú)需等待結(jié)果。換句話說(shuō),基于消息的模型,關(guān)心的是“通知”,而非“處理”。
舉一個(gè)例子,關(guān)于訂單系統(tǒng),訂單最終支付成功之后可能需要給用戶發(fā)送短信積分什么的,但其實(shí)這已經(jīng)不是我們系統(tǒng)的核心流程了。
如果外部系統(tǒng)速度偏慢(比如短信網(wǎng)關(guān)速度不好),那么主流程的時(shí)間會(huì)加長(zhǎng)很多,用戶肯定不希望點(diǎn)擊支付過(guò)好幾分鐘才看到結(jié)果。那么我們只需要通知短信系統(tǒng)“我們支付成功了”,不一定非要等待它立即處理完成。
2.最終一致性
最終一致性指的是兩個(gè)系統(tǒng)的狀態(tài)保持一致,要么都成功,要么都失敗。
當(dāng)然有個(gè)時(shí)間限制,理論上越快越好,但實(shí)際上在各種異常的情況下,可能會(huì)有一定延遲達(dá)到最終一致?tīng)顟B(tài),但最后兩個(gè)系統(tǒng)的狀態(tài)是一樣的。
業(yè)界有一些為“最終一致性”而生的消息隊(duì)列,如:
- Notify(阿里)
- QMQ(去哪兒)等
其設(shè)計(jì)初衷,就是為了交易系統(tǒng)中的高可靠通知。
以一個(gè)銀行的轉(zhuǎn)賬過(guò)程來(lái)理解最終一致性,轉(zhuǎn)賬的需求很簡(jiǎn)單,如果A系統(tǒng)扣錢成功,則B系統(tǒng)加錢一定成功。反之則一起回滾,像什么都沒(méi)發(fā)生一樣。
然而,這個(gè)過(guò)程中存在很多可能的意外:
- A扣錢成功,調(diào)用B加錢接口失敗。
- A扣錢成功,調(diào)用B加錢接口雖然成功,但獲取最終結(jié)果時(shí)網(wǎng)絡(luò)異常引起超時(shí)。
- A扣錢成功,B加錢失敗,A想回滾扣的錢,但A機(jī)器down機(jī)。
可見(jiàn),想把這件看似簡(jiǎn)單的事真正做成,真的不那么容易。
所有跨VM的一致性問(wèn)題,從技術(shù)的角度講通用的解決方案是:
- 強(qiáng)一致性,分布式事務(wù),但落地太難且成本太高,后文會(huì)具體提到。
- 最終一致性,主要是用“記錄”和“補(bǔ)償”的方式。在做所有的不確定的事情之前,先把事情記錄下來(lái),然后去做不確定的事情,結(jié)果可能是:成功、失敗或是不確定,“不確定”(例如超時(shí)等)可以等價(jià)為失敗。成功就可以把記錄的東西清理掉了,對(duì)于失敗和不確定,可以依靠定時(shí)任務(wù)等方式把所有失敗的事情重新搞一遍,直到成功為止。
- 回到剛才的例子,系統(tǒng)在A扣錢成功的情況下,把要給B“通知”這件事記錄在庫(kù)里(為了保證最高的可靠性可以把通知B系統(tǒng)加錢和扣錢成功這兩件事維護(hù)在一個(gè)本地事務(wù)里),通知成功則刪除這條記錄,通知失敗或不確定則依靠定時(shí)任務(wù)補(bǔ)償性地通知我們,直到我們把狀態(tài)更新成正確的為止。
- 整個(gè)這個(gè)模型依然可以基于RPC來(lái)做,但可以抽象成一個(gè)統(tǒng)一的模型,基于消息隊(duì)列來(lái)做一個(gè)“企業(yè)總線”。
- 具體來(lái)說(shuō),本地事務(wù)維護(hù)業(yè)務(wù)變化和通知消息,一起落地(失敗則一起回滾),然后RPC到達(dá)broker,在broker成功落地后,RPC返回成功,本地消息可以刪除。否則本地消息一直靠定時(shí)任務(wù)輪詢不斷重發(fā),這樣就保證了消息可靠落地broker。
- broker往consumer發(fā)送消息的過(guò)程類似,一直發(fā)送消息,直到consumer發(fā)送消費(fèi)成功確認(rèn)。
- 我們先不理會(huì)重復(fù)消息的問(wèn)題,通過(guò)兩次消息落地加補(bǔ)償,下游是一定可以收到消息的。然后依賴狀態(tài)機(jī)版本號(hào)等方式做判重,更新自己的業(yè)務(wù),就實(shí)現(xiàn)了最終一致性。
最終一致性不是消息隊(duì)列的必備特性,但確實(shí)可以依靠消息隊(duì)列來(lái)做最終一致性的事情。
另外,所有不保證100%不丟消息的消息隊(duì)列,理論上無(wú)法實(shí)現(xiàn)最終一致性。好吧,應(yīng)該說(shuō)理論上的100%,排除系統(tǒng)嚴(yán)重故障和bug。
像Kafka一類的設(shè)計(jì),在設(shè)計(jì)層面上就有丟消息的可能(比如定時(shí)刷盤,如果掉電就會(huì)丟消息)。哪怕只丟千分之一的消息,業(yè)務(wù)也必須用其他的手段來(lái)保證結(jié)果正確。
2.廣播
消息隊(duì)列的基本功能之一是進(jìn)行廣播。
如果沒(méi)有消息隊(duì)列,每當(dāng)一個(gè)新的業(yè)務(wù)方接入,我們都要聯(lián)調(diào)一次新接口。有了消息隊(duì)列,我們只需要關(guān)心消息是否送達(dá)了隊(duì)列,至于誰(shuí)希望訂閱,是下游的事情,無(wú)疑極大地減少了開(kāi)發(fā)和聯(lián)調(diào)的工作量。
比如本文開(kāi)始提到的產(chǎn)品中心發(fā)布產(chǎn)品變更的消息,以及景點(diǎn)庫(kù)很多去重更新的消息,可能“關(guān)心”方有很多個(gè),但產(chǎn)品中心和景點(diǎn)庫(kù)只需要發(fā)布變更消息即可,誰(shuí)關(guān)心誰(shuí)接入。
3.錯(cuò)峰與流控
試想上下游對(duì)于事情的處理能力是不同的。
比如,Web前端每秒承受上千萬(wàn)的請(qǐng)求,并不是什么神奇的事情,只需要加多一點(diǎn)機(jī)器,再搭建一些LVS負(fù)載均衡設(shè)備和Nginx等即可。
但數(shù)據(jù)庫(kù)的處理能力卻十分有限,即使使用SSD加分庫(kù)分表,單機(jī)的處理能力仍然在萬(wàn)級(jí)。由于成本的考慮,我們不能奢求數(shù)據(jù)庫(kù)的機(jī)器數(shù)量追上前端。
這種問(wèn)題同樣存在于系統(tǒng)和系統(tǒng)之間,如短信系統(tǒng)可能由于短板效應(yīng),速度卡在網(wǎng)關(guān)上(每秒幾百次請(qǐng)求),跟前端的并發(fā)量不是一個(gè)數(shù)量級(jí)。
但用戶晚上個(gè)半分鐘左右收到短信,一般是不會(huì)有太大問(wèn)題的。如果沒(méi)有消息隊(duì)列,兩個(gè)系統(tǒng)之間通過(guò)協(xié)商、滑動(dòng)窗口等復(fù)雜的方案也不是說(shuō)不能實(shí)現(xiàn)。
但系統(tǒng)復(fù)雜性指數(shù)級(jí)增長(zhǎng),勢(shì)必在上游或者下游做存儲(chǔ),并且要處理定時(shí)、擁塞等一系列問(wèn)題。而且每當(dāng)有處理能力有差距的時(shí)候,都需要單獨(dú)開(kāi)發(fā)一套邏輯來(lái)維護(hù)這套邏輯。所以,利用中間系統(tǒng)轉(zhuǎn)儲(chǔ)兩個(gè)系統(tǒng)的通信內(nèi)容,并在下游系統(tǒng)有能力處理這些消息的時(shí)候,再處理這些消息,是一套相對(duì)較通用的方式。
消息隊(duì)列使用總結(jié)
1.消息隊(duì)列不是萬(wàn)能的,對(duì)于需要強(qiáng)事務(wù)保證而且延遲敏感的,RPC是優(yōu)于消息隊(duì)列的。
2.對(duì)于一些無(wú)關(guān)痛癢,或者對(duì)于別人非常重要但是對(duì)于自己不是那么關(guān)心的事情,可以利用消息隊(duì)列去做。
3.支持最終一致性的消息隊(duì)列,能夠用來(lái)處理延遲不那么敏感的“分布式事務(wù)”場(chǎng)景,而且相對(duì)于笨重的分布式事務(wù),可能是更優(yōu)的處理方式。
4.當(dāng)上下游系統(tǒng)處理能力存在差距的時(shí)候,利用消息隊(duì)列做一個(gè)通用的“漏斗”,在下游有能力處理的時(shí)候,再進(jìn)行分發(fā)。
5.如果下游有很多系統(tǒng)關(guān)心你的系統(tǒng)發(fā)出的通知的時(shí)候,果斷地使用消息隊(duì)列吧。

本文由博客一文多發(fā)平臺(tái) OpenWrite 發(fā)布!