摘要:系統拿不再經過系統,而是從消息隊列里邊拿。高可用無論是我們使用消息隊列來做解耦異步還是削峰,消息隊列肯定不能是單機的。最后本文主要講解了什么是消息隊列,消息隊列可以為我們帶來什么好處,以及一個消息隊列可能會涉及到哪些問題。
前言
只有光頭才能變強。文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3y
公司用到的很多技術,自己之前都沒學過(尬),于是只能慢慢補了。這次給大家寫寫我學習消息隊列的筆記,希望對大家有幫助。
一、什么是消息隊列?消息隊列不知道大家看到這個詞的時候,會不會覺得它是一個比較高端的技術,反正我是覺得它好像是挺牛逼的。
消息隊列,一般我們會簡稱它為MQ(Message Queue),嗯,就是很直白的簡寫。
我們先不管消息(Message)這個詞,來看看隊列(Queue)。這一看,隊列大家應該都熟悉吧。
隊列是一種先進先出的數據結構。
在Java里邊,已經實現了不少的隊列了:
那為什么還需要消息隊列(MQ)這種中間件呢???其實這個問題,跟之前我學Redis的時候很像。Redis是一個以key-value形式存儲的內存數據庫,明明我們可以使用類似HashMap這種實現類就可以達到類似的效果了,那還為什么要Redis?《Redis合集》
到這里,大家可以先猜猜為什么要用消息隊列(MQ)這種中間件,下面會繼續補充。
消息隊列可以簡單理解為:把要傳輸的數據放在隊列中。
)
科普:
把數據放到消息隊列叫做生產者
從消息隊列里邊取數據叫做消費者
二、為什么要用消息隊列?為什么要用消息隊列,也就是在問:用了消息隊列有什么好處。我們看看以下的場景
2.1 解耦現在我有一個系統A,系統A可以產生一個userId
然后,現在有系統B和系統C都需要這個userId去做相關的操作
寫成偽代碼可能是這樣的:
public class SystemA { // 系統B和系統C的依賴 SystemB systemB = new SystemB(); SystemC systemC = new SystemC(); // 系統A獨有的數據userId private String userId = "Java3y"; public void doSomething() { // 系統B和系統C都需要拿著系統A的userId去操作其他的事 systemB.SystemBNeed2do(userId); systemC.SystemCNeed2do(userId); } }
結構圖如下:
ok,一切平安無事度過了幾個天。
某一天,系統B的負責人告訴系統A的負責人,現在系統B的SystemBNeed2do(String userId)這個接口不再使用了,讓系統A別去調它了。
于是,系統A的負責人說"好的,那我就不調用你了。",于是就把調用系統B接口的代碼給刪掉了:
public void doSomething() { // 系統A不再調用系統B的接口了 //systemB.SystemBNeed2do(userId); systemC.SystemCNeed2do(userId); }
又過了幾天,系統D的負責人接了個需求,也需要用到系統A的userId,于是就跑去跟系統A的負責人說:"老哥,我要用到你的userId,你調一下我的接口吧"
于是系統A說:"沒問題的,這就搞"
然后,系統A的代碼如下:
public class SystemA { // 已經不再需要系統B的依賴了 // SystemB systemB = new SystemB(); // 系統C和系統D的依賴 SystemC systemC = new SystemC(); SystemD systemD = new SystemD(); // 系統A獨有的數據 private String userId = "Java3y"; public void doSomething() { // 已經不再需要系統B的依賴了 //systemB.SystemBNeed2do(userId); // 系統C和系統D都需要拿著系統A的userId去操作其他的事 systemC.SystemCNeed2do(userId); systemD.SystemDNeed2do(userId); } }
時間飛逝:
又過了幾天,系統E的負責人過來了,告訴系統A,需要userId。
又過了幾天,系統B的負責人過來了,告訴系統A,還是重新掉那個接口吧。
又過了幾天,系統F的負責人過來了,告訴系統A,需要userId。
…...
于是系統A的負責人,每天都被這給騷擾著,改來改去,改來改去.......
還有另外一個問題,調用系統C的時候,如果系統C掛了,系統A還得想辦法處理。如果調用系統D時,由于網絡延遲,請求超時了,那系統A是反饋fail還是重試??
最后,系統A的負責人,覺得隔一段時間就改來改去,沒意思,于是就跑路了。
然后,公司招來一個大佬,大佬經過幾天熟悉,上來就說:將系統A的userId寫到消息隊列中,這樣系統A就不用經常改動了。為什么呢?下面我們來一起看看:
系統A將userId寫到消息隊列中,系統C和系統D從消息隊列中拿數據。這樣有什么好處?
系統A只負責把數據寫到隊列中,誰想要或不想要這個數據(消息),系統A一點都不關心。
即便現在系統D不想要userId這個數據了,系統B又突然想要userId這個數據了,都跟系統A無關,系統A一點代碼都不用改。
系統D拿userId不再經過系統A,而是從消息隊列里邊拿。系統D即便掛了或者請求超時,都跟系統A無關,只跟消息隊列有關。
這樣一來,系統A與系統B、C、D都解耦了。
2.2 異步我們再來看看下面這種情況:系統A還是直接調用系統B、C、D
代碼如下:
public class SystemA { SystemB systemB = new SystemB(); SystemC systemC = new SystemC(); SystemD systemD = new SystemD(); // 系統A獨有的數據 private String userId ; public void doOrder() { // 下訂單 userId = this.order(); // 如果下單成功,則安排其他系統做一些事 systemB.SystemBNeed2do(userId); systemC.SystemCNeed2do(userId); systemD.SystemDNeed2do(userId); } }
假設系統A運算出userId具體的值需要50ms,調用系統B的接口需要300ms,調用系統C的接口需要300ms,調用系統D的接口需要300ms。那么這次請求就需要50+300+300+300=950ms
并且我們得知,系統A做的是主要的業務,而系統B、C、D是非主要的業務。比如系統A處理的是訂單下單,而系統B是訂單下單成功了,那發送一條短信告訴具體的用戶此訂單已成功,而系統C和系統D也是處理一些小事而已。
那么此時,為了提高用戶體驗和吞吐量,其實可以異步地調用系統B、C、D的接口。所以,我們可以弄成是這樣的:
系統A執行完了以后,將userId寫到消息隊列中,然后就直接返回了(至于其他的操作,則異步處理)。
本來整個請求需要用950ms(同步)
現在將調用其他系統接口異步化,只需要100ms(異步)
(例子可能舉得不太好,但我覺得說明到點子上就行了,見諒。)
2.3削峰/限流我們再來一個場景,現在我們每個月要搞一次大促,大促期間的并發可能會很高的,比如每秒3000個請求。假設我們現在有兩臺機器處理請求,并且每臺機器只能每次處理1000個請求。
那多出來的1000個請求,可能就把我們整個系統給搞崩了...所以,有一種辦法,我們可以寫到消息隊列中:
系統B和系統C根據自己的能夠處理的請求數去消息隊列中拿數據,這樣即便有每秒有8000個請求,那只是把請求放在消息隊列中,去拿消息隊列的消息由系統自己去控制,這樣就不會把整個系統給搞崩。
三、使用消息隊列有什么問題?經過我們上面的場景,我們已經可以發現,消息隊列能做的事其實還是蠻多的。
說到這里,我們先回到文章的開頭,"明明JDK已經有不少的隊列實現了,我們還需要消息隊列中間件呢?"其實很簡單,JDK實現的隊列種類雖然有很多種,但是都是簡單的內存隊列。為什么我說JDK是簡單的內存隊列呢?下面我們來看看要實現消息隊列(中間件)可能要考慮什么問題。
3.1高可用無論是我們使用消息隊列來做解耦、異步還是削峰,消息隊列肯定不能是單機的。試著想一下,如果是單機的消息隊列,萬一這臺機器掛了,那我們整個系統幾乎就是不可用了。
所以,當我們項目中使用消息隊列,都是得集群/分布式的。要做集群/分布式就必然希望該消息隊列能夠提供現成的支持,而不是自己寫代碼手動去實現。
3.2 數據丟失問題我們將數據寫到消息隊列上,系統B和C還沒來得及取消息隊列的數據,就掛掉了。如果沒有做任何的措施,我們的數據就丟了。
學過Redis的都知道,Redis可以將數據持久化磁盤上,萬一Redis掛了,還能從磁盤從將數據恢復過來。同樣地,消息隊列中的數據也需要存在別的地方,這樣才盡可能減少數據的丟失。
那存在哪呢?
磁盤?
數據庫?
Redis?
分布式文件系統?
同步存儲還是異步存儲?
3.3消費者怎么得到消息隊列的數據?消費者怎么從消息隊列里邊得到數據?有兩種辦法:
生產者將數據放到消息隊列中,消息隊列有數據了,主動叫消費者去拿(俗稱push)
消費者不斷去輪訓消息隊列,看看有沒有新的數據,如果有就消費(俗稱pull)
3.4其他除了這些,我們在使用的時候還得考慮各種的問題:
消息重復消費了怎么辦啊?
我想保證消息是絕對有順序的怎么做?
……..
雖然消息隊列給我們帶來了那么多的好處,但同時我們發現引入消息隊列也會提高系統的復雜性。市面上現在已經有不少消息隊列輪子了,每種消息隊列都有自己的特點,選取哪種MQ還得好好斟酌。
最后本文主要講解了什么是消息隊列,消息隊列可以為我們帶來什么好處,以及一個消息隊列可能會涉及到哪些問題。希望給大家帶來一定的幫助。
參考資料:
Kafka簡明教程
https://zhuanlan.zhihu.com/p/37405836
消息隊列使用的四種場景介紹,有圖有解析,一看就懂
https://zhuanlan.zhihu.com/p/55712984
消息隊列設計精要
https://zhuanlan.zhihu.com/p/21479556
消息隊列的使用場景是怎樣的
https://www.zhihu.com/question/34243607
樂于輸出干貨的Java技術公眾號:Java3y。公眾號內有200多篇原創技術文章、海量視頻資源、精美腦圖,不妨來關注一下!
覺得我的文章寫得不錯,不妨點一下贊!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77567.html
摘要:我在前面的文章中也提到了應該怎么做自我介紹與項目介紹,詳情可以查看這篇文章備戰春招秋招系列初出茅廬的程序員該如何準備面試。因此基于事件消息對象驅動的業務架構可以是一系列流程。 showImg(https://user-gold-cdn.xitu.io/2018/11/14/16711ac29c2ae52c?w=928&h=531&f=png&s=798562); 一 消息隊列MQ的...
摘要:交換器和隊列是綁定的,用于消息隊列和交換器之間的關聯。會將消息發送到匹配的隊列中去使用號和號。參考資料消息隊列之什么是消息隊列淺談消息隊列及常見的消息中間件 一、定義 比較官方的說法是是指利用 高效可靠 的 消息傳遞機制 進行與平臺無關的 數據交流,并基于 數據通信 來進行分布式系統的集成。我們可以理解為將要傳輸的數據放在一個隊列中。 二、為什么使用消息隊列 1、解耦(1)發送者和接收...
摘要:通過以上分析我們可以得出消息隊列具有很好的削峰作用的功能即通過異步處理,將短時間高并發產生的事務消息存儲在消息隊列中,從而削平高峰期的并發事務。 該文已加入開源項目:JavaGuide(一份涵蓋大部分Java程序員所需要掌握的核心知識的文檔類項目,Star 數接近 16k)。地址:https://github.com/Snailclimb... 本文內容思維導圖:showImg(ht...
摘要:后續介紹交換機,生產者直接將消息投遞到中。消息,服務器和應用程序之間傳送的數據,由和組成。也稱為消息隊列,保存消息并將它們轉發給消費者。主要是應為和有一個綁定的關系。 showImg(https://img-blog.csdnimg.cn/20190509221741422.gif); showImg(https://img-blog.csdnimg.cn/20190731191914...
閱讀 3510·2023-04-25 14:57
閱讀 2559·2021-11-22 14:56
閱讀 2079·2021-09-29 09:45
閱讀 1761·2021-09-22 15:53
閱讀 3313·2021-08-25 09:41
閱讀 896·2019-08-29 15:22
閱讀 3289·2019-08-29 13:22
閱讀 3121·2019-08-29 13:08