參考
消息隊(duì)列的使用場景是怎樣的?
新手也能看懂,消息隊(duì)列其實(shí)很簡單
消息隊(duì)列設(shè)計(jì)精要
一、例一
假設(shè)用戶在你的軟件中注冊,服務(wù)端收到用戶的注冊請求后,它會做這些操作:
- 校驗(yàn)用戶名等信息,如果沒問題會在數(shù)據(jù)庫中添加一個(gè)用戶記錄
- 如果是用郵箱注冊會給你發(fā)送一封注冊成功的郵件,手機(jī)注冊則會發(fā)送一條短信
- 分析用戶的個(gè)人信息,以便將來向他推薦一些志同道合的人,或向那些人推薦他
- 發(fā)送給用戶一個(gè)包含操作指南的系統(tǒng)通知
- 等等……
但是對于用戶來說,注冊功能實(shí)際只需要第一步,只要服務(wù)端將他的賬戶信息存到數(shù)據(jù)庫中他便可以登錄上去做他想做的事情了。至于其他的事情,非要在這一次請求中全部完成么?值得用戶浪費(fèi)時(shí)間等你處理這些對他來說無關(guān)緊要的事情么?所以實(shí)際當(dāng)?shù)谝徊阶鐾旰?,服?wù)端就可以把其他的操作放入對應(yīng)的消息隊(duì)列中然后馬上返回用戶結(jié)果,由消息隊(duì)列異步的進(jìn)行這些操作。
或者還有一種情況,同時(shí)有大量用戶注冊你的軟件,再高并發(fā)情況下注冊請求開始出現(xiàn)一些問題,例如郵件接口承受不住,或是分析信息時(shí)的大量計(jì)算使cpu滿載,這將會出現(xiàn)雖然用戶數(shù)據(jù)記錄很快的添加到數(shù)據(jù)庫中了,但是卻卡在發(fā)郵件或分析信息時(shí)的情況,導(dǎo)致請求的響應(yīng)時(shí)間大幅增長,甚至出現(xiàn)超時(shí),這就有點(diǎn)不劃算了。面對這種情況一般也是將這些操作放入消息隊(duì)列(生產(chǎn)者消費(fèi)者模型),消息隊(duì)列慢慢的進(jìn)行處理,同時(shí)可以很快的完成注冊請求,不會影響用戶使用其他功能。
所以在軟件的正常功能開發(fā)中,并不需要去刻意的尋找消息隊(duì)列的使用場景,而是當(dāng)出現(xiàn)性能瓶頸時(shí),去查看業(yè)務(wù)邏輯是否存在可以異步處理的耗時(shí)操作,如果存在的話便可以引入消息隊(duì)列來解決。否則盲目的使用消息隊(duì)列可能會增加維護(hù)和開發(fā)的成本卻無法得到可觀的性能提升,那就得不償失了。
二、例二
小紅是小明的姐姐。小紅希望小明多讀書,常尋找好書給小明看,之前的方式是這樣:小紅問小明什么時(shí)候有空,把書給小明送去,并親眼監(jiān)督小明讀完書才走。久而久之,兩人都覺得麻煩。后來的方式改成了:小紅對小明說「我放到書架上的書你都要看」,然后小紅每次發(fā)現(xiàn)不錯(cuò)的書都放到書架上,小明則看到書架上有書就拿下來看。
書架就是一個(gè)消息隊(duì)列,小紅是生產(chǎn)者,小明是消費(fèi)者。這帶來的好處有:
- 1.小紅想給小明書的時(shí)候,不必問小明什么時(shí)候有空,親手把書交給他了,小紅只把書放到書架上就行了。這樣小紅小明的時(shí)間都更自由。
- 2.小紅相信小明的讀書自覺和讀書能力,不必親眼觀察小明的讀書過程,小紅只要做一個(gè)放書的動作,很節(jié)省時(shí)間。
- 3.當(dāng)明天有另一個(gè)愛讀書的小伙伴小強(qiáng)加入,小紅仍舊只需要把書放到書架上,小明和小強(qiáng)從書架上取書即可(唔,姑且設(shè)定成多個(gè)人取一本書可以每人取走一本吧,可能是拷貝電子書或復(fù)印,暫不考慮版權(quán)問題)。
- 4.書架上的書放在那里,小明閱讀速度快就早點(diǎn)看完,閱讀速度慢就晚點(diǎn)看完,沒關(guān)系,比起小紅把書遞給小明并監(jiān)督小明讀完的方式,小明的壓力會小一些。
這就是消息隊(duì)列的四大好處:
1.解耦
每個(gè)成員不必受其他成員影響,可以更獨(dú)立自主,只通過一個(gè)簡單的容器來聯(lián)系。小紅甚至可以不知道從書架上取書的是誰,小明也可以不知道往書架上放書的人是誰,在他們眼里,都只有書架,沒有對方。毫無疑問,與一個(gè)簡單的容器打交道,比與復(fù)雜的人打交道容易一萬倍,小紅小明可以自由自在地追求各自的人生。
2.提速
小紅選擇相信「把書放到書架上,別的我不問」,為自己節(jié)省了大量時(shí)間。小紅很忙,只能抽出五分鐘時(shí)間,但這時(shí)間足夠把書放到書架上了。
3.廣播
小紅只需要勞動一次,就可以讓多個(gè)小伙伴有書可讀,這大大地節(jié)省了她的時(shí)間,也讓新的小伙伴的加入成本很低。
4.削峰
假設(shè)小明讀書很慢,如果采用小紅每給一本書都監(jiān)督小明讀完的方式,小明有壓力,小紅也不耐煩。反正小紅給書的頻率也不穩(wěn)定,如果今明兩天連給了五本,之后隔三個(gè)月才又給一本,那小明只要在三個(gè)月內(nèi)從書架上陸續(xù)取走五本書讀完就行了,壓力就不那么大了。
當(dāng)然,使用消息隊(duì)列也有其成本:
1.引入復(fù)雜度
毫無疑問,「書架」這東西是多出來的,需要地方放它,還需要防盜。
2.暫時(shí)的不一致性
假如媽媽問小紅「小明最近讀了什么書」,在以前的方式里,小紅因?yàn)橛H眼監(jiān)督小明讀完書了,可以底氣十足地告訴媽媽,但新的方式里,小紅回答媽媽之后會心想「小明應(yīng)該會很快看完吧……」這中間存在著一段「媽媽認(rèn)為小明看了某書,而小明其實(shí)還沒看」的時(shí)期,當(dāng)然,小明最終的閱讀狀態(tài)與媽媽的認(rèn)知會是一致的,這就是所謂的「最終一致性」。
那么,該使用消息隊(duì)列的情況需要滿足什么條件呢?
1.生產(chǎn)者不需要從消費(fèi)者處獲得反饋
引入消息隊(duì)列之前的直接調(diào)用,其接口的返回值應(yīng)該為空,這才讓明明下層的動作還沒做,上層卻當(dāng)成動作做完了繼續(xù)往后走——即所謂異步——成為了可能。小紅放完書之后小明到底看了沒有,小紅根本不問,她默認(rèn)他是看了,否則就只能用原來的方法監(jiān)督到看完了。
2.容許短暫的不一致性
媽媽可能會發(fā)現(xiàn)「有時(shí)候據(jù)說小明看了某書,但事實(shí)上他還沒看」,只要媽媽滿意于「反正他最后看了就行」,異步處理就沒問題。如果媽媽對這情況不能容忍,對小紅大發(fā)雷霆,小紅也就不敢用書架方式了。
3.確實(shí)是用了有效果
即解耦、提速、廣播、削峰這些方面的收益,超過放置書架、監(jiān)控書架這些成本。否則如果是盲目照搬,「聽說老趙家買了書架,咱們家也買一個(gè)」,買回來卻沒什么用,只是讓步驟變多了,還不如直接把書遞給對方呢,那就不對了。
三、例三 何時(shí)需要消息隊(duì)列
當(dāng)你需要使用消息隊(duì)列時(shí),首先需要考慮它的必要性。可以使用mq的場景有很多,最常用的幾種,是做業(yè)務(wù)解耦/最終一致性/廣播/錯(cuò)峰流控等。反之,如果需要強(qiáng)一致性,關(guān)注業(yè)務(wù)邏輯的處理結(jié)果,則RPC顯得更為合適。
1.解耦
解耦是消息隊(duì)列要解決的最本質(zhì)問題。所謂解耦,簡單點(diǎn)講就是一個(gè)事務(wù),只關(guān)心核心的流程。而需要依賴其他系統(tǒng)但不那么重要的事情,有通知即可,無需等待結(jié)果。換句話說,基于消息的模型,關(guān)心的是“通知”,而非“處理”。
比如在美團(tuán)旅游,我們有一個(gè)產(chǎn)品中心,產(chǎn)品中心上游對接的是主站、移動后臺、旅游供應(yīng)鏈等各個(gè)數(shù)據(jù)源;下游對接的是篩選系統(tǒng)、API系統(tǒng)等展示系統(tǒng)。當(dāng)上游的數(shù)據(jù)發(fā)生變更的時(shí)候,如果不使用消息系統(tǒng),勢必要調(diào)用我們的接口來更新數(shù)據(jù),就特別依賴產(chǎn)品中心接口的穩(wěn)定性和處理能力。但其實(shí),作為旅游的產(chǎn)品中心,也許只有對于旅游自建供應(yīng)鏈,產(chǎn)品中心更新成功才是他們關(guān)心的事情。而對于團(tuán)購等外部系統(tǒng),產(chǎn)品中心更新成功也好、失敗也罷,并不是他們的職責(zé)所在。他們只需要保證在信息變更的時(shí)候通知到我們就好了。
而我們的下游,可能有更新索引、刷新緩存等一系列需求。對于產(chǎn)品中心來說,這也不是我們的職責(zé)所在。說白了,如果他們定時(shí)來拉取數(shù)據(jù),也能保證數(shù)據(jù)的更新,只是實(shí)時(shí)性沒有那么強(qiáng)。但使用接口方式去更新他們的數(shù)據(jù),顯然對于產(chǎn)品中心來說太過于“重量級”了,只需要發(fā)布一個(gè)產(chǎn)品ID變更的通知,由下游系統(tǒng)來處理,可能更為合理。
再舉一個(gè)例子,對于我們的訂單系統(tǒng),訂單最終支付成功之后可能需要給用戶發(fā)送短信積分什么的,但其實(shí)這已經(jīng)不是我們系統(tǒng)的核心流程了。如果外部系統(tǒng)速度偏慢(比如短信網(wǎng)關(guān)速度不好),那么主流程的時(shí)間會加長很多,用戶肯定不希望點(diǎn)擊支付過好幾分鐘才看到結(jié)果。那么我們只需要通知短信系統(tǒng)“我們支付成功了”,不一定非要等待它處理完成。
2.最終一致性
最終一致性指的是兩個(gè)系統(tǒng)的狀態(tài)保持一致,要么都成功,要么都失敗。當(dāng)然有個(gè)時(shí)間限制,理論上越快越好,但實(shí)際上在各種異常的情況下,可能會有一定延遲達(dá)到最終一致狀態(tài),但最后兩個(gè)系統(tǒng)的狀態(tài)是一樣的。
業(yè)界有一些為“最終一致性”而生的消息隊(duì)列,如Notify(阿里)、QMQ(去哪兒)等,其設(shè)計(jì)初衷,就是為了交易系統(tǒng)中的高可靠通知。
以一個(gè)銀行的轉(zhuǎn)賬過程來理解最終一致性,轉(zhuǎn)賬的需求很簡單,如果A系統(tǒng)扣錢成功,則B系統(tǒng)加錢一定成功。反之則一起回滾,像什么都沒發(fā)生一樣。然而,這個(gè)過程中存在很多可能的意外:
- A扣錢成功,調(diào)用B加錢接口失敗。
- A扣錢成功,調(diào)用B加錢接口雖然成功,但獲取最終結(jié)果時(shí)網(wǎng)絡(luò)異常引起超時(shí)。
- A扣錢成功,B加錢失敗,A想回滾扣的錢,但A機(jī)器down機(jī)。
可見,想把這件看似簡單的事真正做成,真的不那么容易。所有跨VM的一致性問題,從技術(shù)的角度講通用的解決方案是:
- 強(qiáng)一致性,分布式事務(wù),但落地太難且成本太高,后文會具體提到。
- 最終一致性,主要是用“記錄”和“補(bǔ)償”的方式。在做所有的不確定的事情之前,先把事情記錄下來,然后去做不確定的事情,結(jié)果可能是:成功、失敗或是不確定,“不確定”(例如超時(shí)等)可以等價(jià)為失敗。成功就可以把記錄的東西清理掉了,對于失敗和不確定,可以依靠定時(shí)任務(wù)等方式把所有失敗的事情重新搞一遍,直到成功為止。
- 回到剛才的例子,系統(tǒng)在A扣錢成功的情況下,把要給B“通知”這件事記錄在庫里(為了保證最高的可靠性可以把通知B系統(tǒng)加錢和扣錢成功這兩件事維護(hù)在一個(gè)本地事務(wù)里),通知成功則刪除這條記錄,通知失敗或不確定則依靠定時(shí)任務(wù)補(bǔ)償性地通知我們,直到我們把狀態(tài)更新成正確的為止。
- 整個(gè)這個(gè)模型依然可以基于RPC來做,但可以抽象成一個(gè)統(tǒng)一的模型,基于消息隊(duì)列來做一個(gè)“企業(yè)總線”。
- 具體來說,本地事務(wù)維護(hù)業(yè)務(wù)變化和通知消息,一起落地(失敗則一起回滾),然后RPC到達(dá)broker,在broker成功落地后,RPC返回成功,本地消息可以刪除。否則本地消息一直靠定時(shí)任務(wù)輪詢不斷重發(fā),這樣就保證了消息可靠落地broker。
- broker往consumer發(fā)送消息的過程類似,一直發(fā)送消息,直到consumer發(fā)送消費(fèi)成功確認(rèn)。
- 我們先不理會重復(fù)消息的問題,通過兩次消息落地加補(bǔ)償,下游是一定可以收到消息的。然后依賴狀態(tài)機(jī)版本號等方式做判重,更新自己的業(yè)務(wù),就實(shí)現(xiàn)了最終一致性。
最終一致性不是消息隊(duì)列的必備特性,但確實(shí)可以依靠消息隊(duì)列來做最終一致性的事情。另外,所有不保證100%不丟消息的消息隊(duì)列,理論上無法實(shí)現(xiàn)最終一致性。好吧,應(yīng)該說理論上的100%,排除系統(tǒng)嚴(yán)重故障和bug。
像Kafka一類的設(shè)計(jì),在設(shè)計(jì)層面上就有丟消息的可能(比如定時(shí)刷盤,如果掉電就會丟消息)。哪怕只丟千分之一的消息,業(yè)務(wù)也必須用其他的手段來保證結(jié)果正確。
3.廣播
消息隊(duì)列的基本功能之一是進(jìn)行廣播。如果沒有消息隊(duì)列,每當(dāng)一個(gè)新的業(yè)務(wù)方接入,我們都要聯(lián)調(diào)一次新接口。有了消息隊(duì)列,我們只需要關(guān)心消息是否送達(dá)了隊(duì)列,至于誰希望訂閱,是下游的事情,無疑極大地減少了開發(fā)和聯(lián)調(diào)的工作量。
比如本文開始提到的產(chǎn)品中心發(fā)布產(chǎn)品變更的消息,以及景點(diǎn)庫很多去重更新的消息,可能“關(guān)心”方有很多個(gè),但產(chǎn)品中心和景點(diǎn)庫只需要發(fā)布變更消息即可,誰關(guān)心誰接入。
4.錯(cuò)峰與流控
試想上下游對于事情的處理能力是不同的。比如,Web前端每秒承受上千萬的請求,并不是什么神奇的事情,只需要加多一點(diǎn)機(jī)器,再搭建一些LVS負(fù)載均衡設(shè)備和Nginx等即可。但數(shù)據(jù)庫的處理能力卻十分有限,即使使用SSD加分庫分表,單機(jī)的處理能力仍然在萬級。由于成本的考慮,我們不能奢求數(shù)據(jù)庫的機(jī)器數(shù)量追上前端。
這種問題同樣存在于系統(tǒng)和系統(tǒng)之間,如短信系統(tǒng)可能由于短板效應(yīng),速度卡在網(wǎng)關(guān)上(每秒幾百次請求),跟前端的并發(fā)量不是一個(gè)數(shù)量級。但用戶晚上個(gè)半分鐘左右收到短信,一般是不會有太大問題的。如果沒有消息隊(duì)列,兩個(gè)系統(tǒng)之間通過協(xié)商、滑動窗口等復(fù)雜的方案也不是說不能實(shí)現(xiàn)。但系統(tǒng)復(fù)雜性指數(shù)級增長,勢必在上游或者下游做存儲,并且要處理定時(shí)、擁塞等一系列問題。而且每當(dāng)有處理能力有差距的時(shí)候,都需要單獨(dú)開發(fā)一套邏輯來維護(hù)這套邏輯。所以,利用中間系統(tǒng)轉(zhuǎn)儲兩個(gè)系統(tǒng)的通信內(nèi)容,并在下游系統(tǒng)有能力處理這些消息的時(shí)候,再處理這些消息,是一套相對較通用的方式。
5.總結(jié)
總而言之,消息隊(duì)列不是萬能的。對于需要強(qiáng)事務(wù)保證而且延遲敏感的,RPC是優(yōu)于消息隊(duì)列的。
對于一些無關(guān)痛癢,或者對于別人非常重要但是對于自己不是那么關(guān)心的事情,可以利用消息隊(duì)列去做。
支持最終一致性的消息隊(duì)列,能夠用來處理延遲不那么敏感的“分布式事務(wù)”場景,而且相對于笨重的分布式事務(wù),可能是更優(yōu)的處理方式。
當(dāng)上下游系統(tǒng)處理能力存在差距的時(shí)候,利用消息隊(duì)列做一個(gè)通用的“漏斗”。在下游有能力處理的時(shí)候,再進(jìn)行分發(fā)。
如果下游有很多系統(tǒng)關(guān)心你的系統(tǒng)發(fā)出的通知的時(shí)候,果斷地使用消息隊(duì)列吧。
四、如何設(shè)計(jì)一個(gè)消息隊(duì)列
我們現(xiàn)在明確了消息隊(duì)列的使用場景,下一步就是如何設(shè)計(jì)實(shí)現(xiàn)一個(gè)消息隊(duì)列了。
基于消息的系統(tǒng)模型,不一定需要broker(消息隊(duì)列服務(wù)端)。市面上的的Akka(actor模型)、ZeroMQ等,其實(shí)都是基于消息的系統(tǒng)設(shè)計(jì)范式,但是沒有broker。
我們之所以要設(shè)計(jì)一個(gè)消息隊(duì)列,并且配備broker,無外乎要做兩件事情:
- 消息的轉(zhuǎn)儲,在更合適的時(shí)間點(diǎn)投遞,或者通過一系列手段輔助消息最終能送達(dá)消費(fèi)機(jī)。
- 規(guī)范一種范式和通用的模式,以滿足解耦、最終一致性、錯(cuò)峰等需求。
- 掰開了揉碎了看,最簡單的消息隊(duì)列可以做成一個(gè)消息轉(zhuǎn)發(fā)器,把一次RPC做成兩次RPC。發(fā)送者把消息投遞到服務(wù)端(以下簡稱broker),服務(wù)端再將消息轉(zhuǎn)發(fā)一手到接收端,就是這么簡單。
一般來講,設(shè)計(jì)消息隊(duì)列的整體思路是先build一個(gè)整體的數(shù)據(jù)流,例如producer發(fā)送給broker,broker發(fā)送給consumer,consumer回復(fù)消費(fèi)確認(rèn),broker刪除/備份消息等。
利用RPC將數(shù)據(jù)流串起來。然后考慮RPC的高可用性,盡量做到無狀態(tài),方便水平擴(kuò)展。
之后考慮如何承載消息堆積,然后在合適的時(shí)機(jī)投遞消息,而處理堆積的最佳方式,就是存儲,存儲的選型需要綜合考慮性能/可靠性和開發(fā)維護(hù)成本等諸多因素。
為了實(shí)現(xiàn)廣播功能,我們必須要維護(hù)消費(fèi)關(guān)系,可以利用zk/config server等保存消費(fèi)關(guān)系。
在完成了上述幾個(gè)功能后,消息隊(duì)列基本就實(shí)現(xiàn)了。然后我們可以考慮一些高級特性,如可靠投遞,事務(wù)特性,性能優(yōu)化等。
下面我們會以設(shè)計(jì)消息隊(duì)列時(shí)重點(diǎn)考慮的模塊為主線,穿插灌輸一些消息隊(duì)列的特性實(shí)現(xiàn)方法,來具體分析設(shè)計(jì)實(shí)現(xiàn)一個(gè)消息隊(duì)列時(shí)的方方面面。
后面參見原文……
五、常見的消息隊(duì)列對比
參考消息隊(duì)列的流派之爭
這篇文章的標(biāo)題很難起,網(wǎng)上一翻全是各種MQ的性能比較,很容易讓人以為我也是這么“粗俗”的人(o(╯□╰)o)。我這篇文章想要表達(dá)的是——它們根本不是一個(gè)東西,有毛的性能好比較?
1.MQ是什么
Message Queue(MQ),消息隊(duì)列中間件。很多人都說:MQ通過將消息的發(fā)送和接收分離來實(shí)現(xiàn)應(yīng)用程序的異步和解偶,這個(gè)給人的直覺是——MQ是異步的,用來解耦的,但是這個(gè)只是MQ的效果而不是目的。MQ真正的目的是為了通訊,屏蔽底層復(fù)雜的通訊協(xié)議,定義了一套應(yīng)用層的、更加簡單的通訊協(xié)議。一個(gè)分布式系統(tǒng)中兩個(gè)模塊之間通訊要么是HTTP,要么是自己開發(fā)的TCP,但是這兩種協(xié)議其實(shí)都是原始的協(xié)議。HTTP協(xié)議很難實(shí)現(xiàn)兩端通訊——模塊A可以調(diào)用B,B也可以主動調(diào)用A,如果要做到這個(gè)兩端都要背上WebServer,而且還不支持長連接(HTTP 2.0的庫根本找不到)。TCP就更加原始了,粘包、心跳、私有的協(xié)議,想一想頭皮就發(fā)麻。MQ所要做的就是在這些協(xié)議之上構(gòu)建一個(gè)簡單的“協(xié)議”——生產(chǎn)者/消費(fèi)者模型。MQ帶給我的“協(xié)議”不是具體的通訊協(xié)議,而是更高層次通訊模型。它定義了兩個(gè)對象——發(fā)送數(shù)據(jù)的叫生產(chǎn)者;消費(fèi)數(shù)據(jù)的叫消費(fèi)者, 提供一個(gè)SDK讓我們可以定義自己的生產(chǎn)者和消費(fèi)者實(shí)現(xiàn)消息通訊而無視底層通訊協(xié)議。
2.MQ的流派
列出功能表來比較MQ差異或者來一場“MQ性能大比武”的做法都是比較扯的,首先要做的事情應(yīng)該是分類。我理解的MQ分為兩個(gè)流派
(1).有broker
這個(gè)流派通常有一臺服務(wù)器作為Broker,所有的消息都通過它中轉(zhuǎn)。生產(chǎn)者把消息發(fā)送給它就結(jié)束自己的任務(wù)了,Broker則把消息主動推送給消費(fèi)者(或者消費(fèi)者主動輪詢)。
- 重Topic流
kafka、JMS就屬于這個(gè)流派,生產(chǎn)者會發(fā)送key和數(shù)據(jù)到Broker,由Broker比較key之后決定給那個(gè)消費(fèi)者。這種模式是我們最常見的模式,是我們對MQ最多的印象。在這種模式下一個(gè)topic往往是一個(gè)比較大的概念,甚至一個(gè)系統(tǒng)中就可能只有一個(gè)topic,topic某種意義上就是queue,生產(chǎn)者發(fā)送key相當(dāng)于說:“hi,把數(shù)據(jù)放到key的隊(duì)列中”。

如上圖所示,Broker定義了三個(gè)隊(duì)列,key1,key2,key3,生產(chǎn)者發(fā)送數(shù)據(jù)的時(shí)候會發(fā)送key1和data,Broker在推送數(shù)據(jù)的時(shí)候則推送data(也可能把key帶上)。
雖然架構(gòu)一樣但是kafka的性能要比jms的性能不知道高到多少倍,所以基本這種類型的MQ只有kafka一種備選方案。如果你需要一條暴力的數(shù)據(jù)流(在乎性能而非靈活性)那么kafka是最好的選擇。
- 輕Topic流
這種的代表是RabbitMQ(或者說是AMQP)。生產(chǎn)者發(fā)送key和數(shù)據(jù),消費(fèi)者定義訂閱的隊(duì)列,Broker收到數(shù)據(jù)之后會通過一定的邏輯計(jì)算出key對應(yīng)的隊(duì)列,然后把數(shù)據(jù)交給隊(duì)列。
AMQP,即Advanced Message Queuing Protocol,一個(gè)提供統(tǒng)一消息服務(wù)的應(yīng)用層標(biāo)準(zhǔn)高級消息隊(duì)列協(xié)議,是應(yīng)用層協(xié)議的一個(gè)開放標(biāo)準(zhǔn),為面向消息的中間件設(shè)計(jì)?;诖藚f(xié)議的客戶端與消息中間件可傳遞消息,并不受客戶端/中間件不同產(chǎn)品,不同的開發(fā)語言等條件的限制。Erlang中的實(shí)現(xiàn)有RabbitMQ等。

注意到了嗎?這種模式下解耦了key和queue,在這種架構(gòu)中queue是非常輕量級的(在RabbitMQ中它的上限取決于你的內(nèi)存),消費(fèi)者關(guān)心的只是自己的queue;生產(chǎn)者不必關(guān)心數(shù)據(jù)最終給誰只要指定key就行了,中間的那層映射在AMQP中叫exchange(交換機(jī))。AMQP中有四種種exchange——Direct exchange:key就等于queue;Fanout exchange:無視key,給所有的queue都來一份;Topic exchange:key可以用“寬字符”模糊匹配queue;最后一個(gè)厲害了Headers exchange:無視key,通過查看消息的頭部元數(shù)據(jù)來決定發(fā)給那個(gè)queue(AMQP頭部元數(shù)據(jù)非常豐富而且可以自定義)。
這種結(jié)構(gòu)的架構(gòu)給通訊帶來了很大的靈活性,我們能想到的通訊方式都可以用這四種exchange表達(dá)出來。如果你需要一個(gè)企業(yè)數(shù)據(jù)總線(在乎靈活性)那么RabbitMQ絕對的值得一用。
(2).無broker
此門派是AMQP的“叛徒”,某位道友嫌棄AMQP太“重”(那是他沒看到用Erlang實(shí)現(xiàn)的時(shí)候是多么的行云流水) 所以設(shè)計(jì)了zeromq。這位道友非常睿智,他非常敏銳的意識到——MQ是更高級的Socket,它是解決通訊問題的。所以ZeroMQ被設(shè)計(jì)成了一個(gè)“庫”而不是一個(gè)中間件,這種實(shí)現(xiàn)也可以達(dá)到——沒有broker的目的。

節(jié)點(diǎn)之間通訊的消息都是發(fā)送到彼此的隊(duì)列中,每個(gè)節(jié)點(diǎn)都既是生產(chǎn)者又是消費(fèi)者。ZeroMQ做的事情就是封裝出一套類似于scoket的API可以完成發(fā)送數(shù)據(jù),讀取數(shù)據(jù)。如果你仔細(xì)想一下其實(shí)ZeroMQ是這樣的

頓悟了嗎?Actor模型,ZeroMQ其實(shí)就是一個(gè)跨語言的、重量級的Actor模型郵箱庫。你可以把自己的程序想象成一個(gè)actor,zeromq就是提供郵箱功能的庫;zeromq可以實(shí)現(xiàn)同一臺機(jī)器的IPC通訊也可以實(shí)現(xiàn)不同機(jī)器的TCP、UDP通訊。如果你需要一個(gè)強(qiáng)大的、靈活、野蠻的通訊能力,別猶豫zeromq。
(3).MQ只能異步嗎
答案是否定了,首先ZeroMQ支持請求->應(yīng)答模式;其次RabbitMQ提供了RPC是地地道道的同步通訊,只有JMS、kafka這種架構(gòu)才只能做異步。我們很多人第一次接觸MQ都是JMS之類的這種所以才會產(chǎn)生這種錯(cuò)覺。
(4).總結(jié)
kafka,zeromq,rabbitmq代表了三種完全不同風(fēng)格的MQ架構(gòu);關(guān)注點(diǎn)完全不同:
- kafka在乎的是性能,速度
- rabbitmq追求的是靈活
- zeromq追求的是輕量級、分布式
如果你拿zeromq來做大數(shù)據(jù)量的傳輸功能,不是生產(chǎn)者的內(nèi)存“爆掉”就是消費(fèi)者被“壓死”;如果你用kafka做通訊總線那絕對的不會快只能更慢;你想要rabbitmq實(shí)現(xiàn)分布式,那真的是難為它。

3.參考消息隊(duì)列選型首選Kafka
在這種方案中,引入了基于生產(chǎn)消費(fèi)模型的消息隊(duì)列后,天然具備了負(fù)載均衡能力。同時(shí),隊(duì)列中的消息是由服務(wù)實(shí)例自行拉取的,所以不論是接入層,還是消息隊(duì)列,都不要關(guān)心服務(wù)實(shí)例的部署細(xì)節(jié)。服務(wù)實(shí)例也可以處行控制拉取消息的頻率,實(shí)現(xiàn)并發(fā)控制。
但由于引入消息隊(duì)列后,每一次的請求都不會被直接響應(yīng),這對于異步請求沒有問題,但對于同步請求,處理起來就有些麻煩了。還好,在一些成熟的消息隊(duì)列方案中,會提供request-reply模式,用于處理同步請求。這也是后我們進(jìn)行技術(shù)選型的重要考慮因素。
舉個(gè)例子:假設(shè),A模塊通過MQ發(fā)布消息給B模塊和C模塊,B模塊與C模塊收到消息后,需要通過MQ向A模塊發(fā)送確認(rèn)消息。這時(shí)MQ需要能夠自動的將B和C的應(yīng)答消息轉(zhuǎn)發(fā)給A,而不是其它模塊。這個(gè)也是實(shí)現(xiàn)前面提到的request-reply模式的關(guān)健所在。
4.參考爬蟲架構(gòu) | 消息隊(duì)列應(yīng)用場景及ActiveMQ、RabbitMQ、RocketMQ、Kafka對比
- 生產(chǎn)者消費(fèi)者模式(Producer-Consumer)
ActiveMQ-支持,RabbitMQ-支持,RocketMQ-支持,Kafka-支持。 - 發(fā)布訂閱模式(Publish-Subscribe)
ActiveMQ-支持,RabbitMQ-支持,RocketMQ-支持,Kafka-支持。 - 請求回應(yīng)模型(Request-Reply)
ActiveMQ-支持,RabbitMQ-支持,RocketMQ-不支持,Kafka-不支持。 - API完備性
ActiveMQ-高,RabbitMQ-高,RocketMQ-高,Kafka-高。 - 多語言支持
ActiveMQ-支持,RabbitMQ-支持,RocketMQ-只支持JAVA,Kafka-支持。 - 單機(jī)吞吐量
ActiveMQ-萬級,RabbitMQ-萬級,RocketMQ-萬級,Kafka-十萬級。 - 消息延遲
ActiveMQ-無,RabbitMQ-微秒級,RocketMQ-毫秒級,Kafka-毫秒級。 - 可用性
ActiveMQ-高(主從),RabbitMQ-高(主從),RocketMQ-非常高(分布式),Kafka-非常高(分布式)。 - 消息丟失
ActiveMQ-低,RabbitMQ-低,RocketMQ-理論上不會丟失,Kafka-理論上不會丟失。 - 文檔的完備性
ActiveMQ-高,RabbitMQ-高,RocketMQ-高,Kafka-高。 - 提供快速入門
ActiveMQ-有,RabbitMQ-有,RocketMQ-有,Kafka-有。 - 社區(qū)活躍度
ActiveMQ-高,RabbitMQ-高,RocketMQ-中,Kafka-高。 - 商業(yè)支持
ActiveMQ-無,RabbitMQ-無,RocketMQ-阿里云,Kafka-阿里云。
六、golang mq
1.rabbitmq的streadway/amqp
在https://www.rabbitmq.com/getstarted.html可以看到go語言版本指向了"github.com/streadway/amqp"
Go語言實(shí)現(xiàn)RabbitMQ入門系列的目錄,有興趣的可以參考一下:
原文地址提供了其他語言的實(shí)現(xiàn)版本,可參考RabbtMQ GetStarted.
2.golang nsq
NSQ是Go語言編寫的,開源的分布式消息隊(duì)列中間件,其設(shè)計(jì)的目的是用來大規(guī)模地處理每天數(shù)以十億計(jì)級別的消息。NSQ 具有分布式和去中心化拓?fù)浣Y(jié)構(gòu),該結(jié)構(gòu)具有無單點(diǎn)故障、故障容錯(cuò)、高可用性以及能夠保證消息的可靠傳遞的特征,是一個(gè)成熟的、已在大規(guī)模生成環(huán)境下應(yīng)用的產(chǎn)品。
1.1 Features
1). Distributed
NSQ提供了分布式的,去中心化,且沒有單點(diǎn)故障的拓?fù)浣Y(jié)構(gòu),穩(wěn)定的消息傳輸發(fā)布保障,能夠具有高容錯(cuò)和HA(高可用)特性。
2). Scalable易于擴(kuò)展
NSQ支持水平擴(kuò)展,沒有中心化的brokers。內(nèi)置的發(fā)現(xiàn)服務(wù)簡化了在集群中增加節(jié)點(diǎn)。同時(shí)支持pub-sub和load-balanced 的消息分發(fā)。
3). Ops Friendly
NSQ非常容易配置和部署,生來就綁定了一個(gè)管理界面。二進(jìn)制包沒有運(yùn)行時(shí)依賴。官方有Docker image。
4).Integrated高度集成
官方的 Go 和 Python庫都有提供。而且為大多數(shù)語言提供了庫。