一文理解為什么需要使用消息隊(duì)列
消息隊(duì)列(Message Queue),簡(jiǎn)稱(chēng)為MQ,是一種跨進(jìn)程的通信機(jī)制,用于上下游傳遞消息。常見(jiàn)消息隊(duì)列中間件如:Kafka、ActiveMQ、RabbitMQ、RocketMQ等。
消息隊(duì)列引入對(duì)系統(tǒng)的優(yōu)勢(shì)
1. 解耦
在未使用消息隊(duì)列的系統(tǒng)中,系統(tǒng)間耦合性太強(qiáng)。如下圖所示的業(yè)務(wù)場(chǎng)景,系統(tǒng)A在代碼中直接調(diào)用系統(tǒng)B和系統(tǒng)C的代碼,如果將來(lái)D系統(tǒng)接入或者B系統(tǒng)取消,系統(tǒng)A還需要修改代碼,造成系統(tǒng)風(fēng)險(xiǎn)。
在這個(gè)場(chǎng)景中,A系統(tǒng)與其它的系統(tǒng)嚴(yán)重耦合,A系統(tǒng)要考慮各個(gè)下游系統(tǒng)如果掛掉的話(huà)的失敗重試或兜底策略。

在使用消息隊(duì)列后,將下游需要的消息push到消息列隊(duì)中,需要消息的系統(tǒng)自己從消息隊(duì)列中訂閱;如果某個(gè)系統(tǒng)不需要這條數(shù)據(jù)了,就取消對(duì) MQ 消息的訂閱即可,從而系統(tǒng)A不需要做任何修改,也不需要考慮下游消費(fèi)失敗的情況。

通過(guò)引入消息隊(duì)列的Pub/Sub發(fā)布訂閱消息,A系統(tǒng)就與其它系統(tǒng)徹底解耦。這樣也解決了大系統(tǒng)中多部門(mén)或者多人協(xié)作的職責(zé)分離問(wèn)題,減少事故的發(fā)生。
2. 異步
在未使用消息隊(duì)列的系統(tǒng)中,一些非必要的業(yè)務(wù)邏輯以同步的方式運(yùn)行,耗費(fèi)大量時(shí)間。
如下圖所示的業(yè)務(wù)場(chǎng)景,A 系統(tǒng)接收一個(gè)請(qǐng)求,自身運(yùn)算話(huà)費(fèi)30ms,還需要在BCD進(jìn)行運(yùn)算(均需要100ms)。最終請(qǐng)求總延時(shí)是 330ms,如果A系統(tǒng)是網(wǎng)關(guān),接收到請(qǐng)求是toc的用戶(hù)請(qǐng)求,更高的延時(shí)將導(dǎo)致更差的用戶(hù)體驗(yàn)。
一般互聯(lián)網(wǎng)類(lèi)的企業(yè),對(duì)于用戶(hù)直接的操作,一般要求是每個(gè)請(qǐng)求都必須在 200 ms 以?xún)?nèi)完成,對(duì)用戶(hù)幾乎是無(wú)感知的。

在使用消息隊(duì)列后,將系統(tǒng)A的消息寫(xiě)入消息隊(duì)列,非必要的業(yè)務(wù)邏輯BCD以異步的方式運(yùn)行,這樣總耗時(shí)就降低到30ms,提升了10倍的響應(yīng)速度。

使用消息隊(duì)列進(jìn)行異步優(yōu)化的時(shí)候要熟悉業(yè)務(wù)場(chǎng)景,并不是所有業(yè)務(wù)場(chǎng)景都可以用消息隊(duì)列進(jìn)行異步優(yōu)化。
3. 削峰
在未使用消息隊(duì)列的系統(tǒng)中,系統(tǒng)面對(duì)突發(fā)大流量會(huì)導(dǎo)致系統(tǒng)崩潰。如下圖所示的業(yè)務(wù)場(chǎng)景,當(dāng)突發(fā)并發(fā)大流量的時(shí)候,所有的請(qǐng)求直接懟到數(shù)據(jù)庫(kù),造成數(shù)據(jù)庫(kù)連接異常。
在未使用消息隊(duì)列且同時(shí)沒(méi)有彈性伸縮的系統(tǒng)中,如果突發(fā)大流量,即使下游不是數(shù)據(jù)庫(kù),也會(huì)因?yàn)橄M(fèi)能力不足導(dǎo)致請(qǐng)求大量超時(shí),系統(tǒng)宕機(jī)最終導(dǎo)致分布式系統(tǒng)的雪崩效應(yīng)。

在使用消息隊(duì)列后,系統(tǒng)A按照數(shù)據(jù)庫(kù)(或下游系統(tǒng))能處理的并發(fā)量,從消息隊(duì)列中慢慢拉取消息。在大多數(shù)生產(chǎn)系統(tǒng)的業(yè)務(wù)場(chǎng)景中,短暫的高峰期的消息積壓是允許的。這樣就可以用有限的機(jī)器資源承載高并發(fā)請(qǐng)求。
如果是下游系統(tǒng)處理能力有限,能增加彈性擴(kuò)容的基礎(chǔ)設(shè)施能力,那當(dāng)然是最好的,但是彈性擴(kuò)容的響應(yīng)速度有限,如果不能應(yīng)對(duì)突發(fā)的流量高峰的話(huà),還是推薦使用消息隊(duì)列進(jìn)行削峰操作(或者可以的話(huà),使用降級(jí)熔斷)。

消息隊(duì)列引入對(duì)系統(tǒng)的劣勢(shì)
雖然消息隊(duì)列有上面三種優(yōu)勢(shì),但是并不是盲目使用的。
系統(tǒng)可用性降低
系統(tǒng)每增加一個(gè)組件,必然導(dǎo)致可用性降低。畢竟沒(méi)有一個(gè)組件可以保證100%可用性,因此還需要在消息隊(duì)列高可用方面花費(fèi)投入。
系統(tǒng)復(fù)雜性增加
在使用消息隊(duì)列后,會(huì)增加很多方面的問(wèn)題,比如如何保證消息不被重復(fù)消費(fèi)、如何保證消息可靠傳輸、如何保證數(shù)據(jù)一致性問(wèn)題和如何解決海量消息的積壓故障。因此,需要考慮的東西更多,系統(tǒng)復(fù)雜性增大。
但上面出現(xiàn)的問(wèn)題,都是有比較成熟的解決方案的,之后博客會(huì)逐個(gè)講解。
什么時(shí)候不能使用消息隊(duì)列
最后再講下,什么時(shí)候不能使用消息隊(duì)列。
上游請(qǐng)求到來(lái)之后,系統(tǒng)A調(diào)用系統(tǒng)B并需要知道B的執(zhí)行結(jié)果。這種業(yè)務(wù)場(chǎng)景下通常不能使用消息隊(duì)列,而使用RPC調(diào)用。

這種業(yè)務(wù)場(chǎng)景下使用消息隊(duì)列,會(huì)導(dǎo)致系統(tǒng)A與系統(tǒng)B的信息割裂。

技術(shù)選型
原本想列出具體消息隊(duì)列的優(yōu)劣勢(shì)對(duì)比,但由于版本不斷更迭,很快這塊內(nèi)容會(huì)失去意義,所以只講講選型的原則。首先是要基于團(tuán)隊(duì)成員的技術(shù)棧以及部門(mén)公司的技術(shù)棧來(lái)進(jìn)行選擇,其次是根據(jù)業(yè)務(wù)場(chǎng)景:由于Kafka在大數(shù)據(jù)業(yè)務(wù)中有著無(wú)可爭(zhēng)議的優(yōu)勢(shì),所以如果在線(xiàn)業(yè)務(wù)只是需要數(shù)據(jù)流轉(zhuǎn),Kafka完全可以同時(shí)兼顧在線(xiàn)業(yè)務(wù)和大數(shù)據(jù)業(yè)務(wù),保證技術(shù)棧單一,便于維護(hù);如果需要復(fù)雜的消息隊(duì)列功能,可以根據(jù)版本對(duì)應(yīng)的功能,從RabbitMQ和RocketMQ做選型。
注:
ActiveMQ的社區(qū)活躍已經(jīng)大不如前,而開(kāi)源項(xiàng)目RabbitMQ的社區(qū)依舊活躍。所以新項(xiàng)目不需要考慮前者。
RocketMQ使用Java開(kāi)發(fā),RabbitMQ使用Erlang開(kāi)發(fā)。前者對(duì)于廣大Java后端更為友好些,畢竟方便深入源碼。同時(shí)作為阿里的開(kāi)源項(xiàng)目,一段時(shí)間內(nèi)的技術(shù)可靠性還是有保證的,如果阿里放棄維護(hù),也會(huì)有很多java社區(qū)貢獻(xiàn)者來(lái)繼續(xù)完善;后者的社區(qū)活躍與技術(shù)成熟度都要比前者高,且有大量互聯(lián)網(wǎng)公司的成功案例,所以?xún)蓚€(gè)各有利弊,選型的時(shí)候需要仔細(xì)思考。
總結(jié)
作為后端開(kāi)發(fā),一定要熟悉消息隊(duì)列的優(yōu)缺點(diǎn),并針對(duì)引入消息隊(duì)列可能引發(fā)的系統(tǒng)問(wèn)題作出相應(yīng)的方案設(shè)計(jì),保證系統(tǒng)高可用、高可靠的運(yùn)行,以及保證數(shù)據(jù)的一致性。
