什麼是消息隊列?

前言

只有光頭才能變強。

文本已收錄至個人GitHub倉庫,歡迎Star:https://github.com/ZhongFuCheng3y/3yhtml

公司用到的不少技術,本身以前都沒學過(),因而只能慢慢補了。此次給你們寫寫我學習消息隊列的筆記,但願對你們有幫助。java

1、什麼是消息隊列?

消息隊列不知道你們看到這個詞的時候,會不會以爲它是一個比較高端的技術,反正我是以爲它好像是挺牛逼的。git

消息隊列,通常咱們會簡稱它爲MQ(Message Queue),嗯,就是很直白的簡寫。

咱們先無論消息(Message)這個詞,來看看隊列(Queue)。這一看,隊列你們應該都熟悉吧。github

隊列是一種 先進先出的數據結構。

先進先出

在Java裏邊,已經實現了很多的隊列了:數據庫

Java的隊列實現類

那爲何還須要消息隊列(MQ)這種中間件呢???其實這個問題,跟以前我學Redis的時候很像。Redis是一個以key-value形式存儲的內存數據庫,明明咱們可使用相似HashMap這種實現類就能夠達到相似的效果了,那還爲何要Redis?《Redis合集網絡

  • 到這裏,你們能夠先猜猜爲何要用消息隊列(MQ)這種中間件,下面會繼續補充。

消息隊列能夠簡單理解爲:把要傳輸的數據放在隊列中數據結構

圖片來源:https://www.cloudamqp.com/blog/2014-12-03-what-is-message-queuing.html)併發

科普:異步

  • 把數據放到消息隊列叫作生產者
  • 從消息隊列裏邊取數據叫作消費者

2、爲何要用消息隊列?

爲何要用消息隊列,也就是在問:用了消息隊列有什麼好處。咱們看看如下的場景分佈式

2.1 解耦

如今我有一個系統A,系統A能夠產生一個userId

系統A能夠產生一個UserId

而後,如今有系統B和系統C都須要這個userId去作相關的操做

系統A給系統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須要調用系統D的接口

而後,系統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將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的接口。因此,咱們能夠弄成是這樣的:

此時才用了100ms

系統A執行完了之後,將userId寫到消息隊列中,而後就直接返回了(至於其餘的操做,則異步處理)。

  • 原本整個請求須要用950ms(同步)
  • 如今將調用其餘系統接口異步化,只須要100ms(異步)

(例子可能舉得不太好,但我以爲說明到點子上就好了,見諒。)

2.3削峯/限流

咱們再來一個場景,如今咱們每月要搞一次大促,大促期間的併發可能會很高的,好比每秒3000個請求。假設咱們如今有兩臺機器處理請求,而且每臺機器只能每次處理1000個請求。

削峯的場景

那多出來的1000個請求,可能就把咱們整個系統給搞崩了...因此,有一種辦法,咱們能夠寫到消息隊列中:

寫到消息隊列中,系統從消息隊列中拿到請求

系統B和系統C根據本身的可以處理的請求數去消息隊列中拿數據,這樣即使有每秒有8000個請求,那只是把請求放在消息隊列中,去拿消息隊列的消息由系統本身去控制,這樣就不會把整個系統給搞崩。

3、使用消息隊列有什麼問題?

通過咱們上面的場景,咱們已經能夠發現,消息隊列能作的事其實仍是蠻多的。

說到這裏,咱們先回到文章的開頭,"明明JDK已經有很多的隊列實現了,咱們還須要消息隊列中間件呢?"其實很簡單,JDK實現的隊列種類雖然有不少種,可是都是簡單的內存隊列。爲何我說JDK是簡單的內存隊列呢?下面咱們來看看要實現消息隊列(中間件)可能要考慮什麼問題

3.1高可用

不管是咱們使用消息隊列來作解耦、異步仍是削峯,消息隊列確定不能是單機的。試着想一下,若是是單機的消息隊列,萬一這臺機器掛了,那咱們整個系統幾乎就是不可用了。

萬一單機的隊列掛掉了

因此,當咱們項目中使用消息隊列,都是得集羣/分佈式的。要作集羣/分佈式就必然但願該消息隊列可以提供現成的支持,而不是本身寫代碼手動去實現。

3.2 數據丟失問題

咱們將數據寫到消息隊列上,系統B和C還沒來得及取消息隊列的數據,就掛掉了。若是沒有作任何的措施,咱們的數據就丟了

數據丟失問題

學過Redis的都知道,Redis能夠將數據持久化磁盤上,萬一Redis掛了,還能從磁盤從將數據恢復過來。一樣地,消息隊列中的數據也須要存在別的地方,這樣才儘量減小數據的丟失。

那存在哪呢?

  • 磁盤?
  • 數據庫?
  • Redis?
  • 分佈式文件系統?

同步存儲仍是異步存儲?

3.3消費者怎麼獲得消息隊列的數據?

消費者怎麼從消息隊列裏邊獲得數據?有兩種辦法:

  • 生產者將數據放到消息隊列中,消息隊列有數據了,主動叫消費者去拿(俗稱push)
  • 消費者不斷去輪訓消息隊列,看看有沒有新的數據,若是有就消費(俗稱pull)

3.4其餘

除了這些,咱們在使用的時候還得考慮各類的問題:

  • 消息重複消費了怎麼辦啊?
  • 我想保證消息是絕對有順序的怎麼作?
  • ……..

雖然消息隊列給咱們帶來了那麼多的好處,但同時咱們發現引入消息隊列也會提升系統的複雜性。市面上如今已經有很多消息隊列輪子了,每種消息隊列都有本身的特色,選取哪一種MQ還得好好斟酌

最後

本文主要講解了什麼是消息隊列,消息隊列能夠爲咱們帶來什麼好處,以及一個消息隊列可能會涉及到哪些問題。但願給你們帶來必定的幫助。

參考資料:

樂於輸出 乾貨的Java技術公衆號:Java3y。公衆號內有200多篇 原創技術文章、海量視頻資源、精美腦圖,不妨來 關注一下!

帥的人都關注了

以爲個人文章寫得不錯,不妨點一下

相關文章
相關標籤/搜索