RabbitMQ的應用場景以及基本原理介紹

八重櫻發表於2020-07-03

1.背景

RabbitMQ是一個由erlang開發的AMQP(Advanved Message Queue)的開源實現。

2.應用場景

2.1非同步處理

場景說明:使用者註冊後,需要發註冊郵件和註冊簡訊,傳統的做法有兩種1.序列的方式;2.並行的方式 
(1)序列方式:將註冊資訊寫入資料庫後,傳送註冊郵件,再傳送註冊簡訊,以上三個任務全部完成後才返回給客戶端。 這有一個問題是,郵件,簡訊並不是必須的,它只是一個通知,而這種做法讓客戶端等待沒有必要等待的東西. 
這裡寫圖片描述
(2)並行方式:將註冊資訊寫入資料庫後,傳送郵件的同時,傳送簡訊,以上三個任務完成後,返回給客戶端,並行的方式能提高處理的時間。 
這裡寫圖片描述
假設三個業務節點分別使用50ms,序列方式使用時間150ms,並行使用時間100ms。雖然並性已經提高的處理時間,但是,前面說過,郵件和簡訊對我正常的使用網站沒有任何影響,客戶端沒有必要等著其傳送完成才顯示註冊成功,英愛是寫入資料庫後就返回. 
(3)訊息佇列 
引入訊息佇列後,把傳送郵件,簡訊不是必須的業務邏輯非同步處理 
這裡寫圖片描述
由此可以看出,引入訊息佇列後,使用者的響應時間就等於寫入資料庫的時間+寫入訊息佇列的時間(可以忽略不計),引入訊息佇列後處理後,響應時間是序列的3倍,是並行的2倍。

2.2 應用解耦

場景:雙11是購物狂節,使用者下單後,訂單系統需要通知庫存系統,傳統的做法就是訂單系統呼叫庫存系統的介面. 
這裡寫圖片描述 
這種做法有一個缺點:

  • 當庫存系統出現故障時,訂單就會失敗。(這樣馬雲將少賺好多好多錢^ ^)

  • 訂單系統和庫存系統高耦合. 
    引入訊息佇列 
    這裡寫圖片描述

  • 訂單系統:使用者下單後,訂單系統完成持久化處理,將訊息寫入訊息佇列,返回使用者訂單下單成功。

  • 庫存系統:訂閱下單的訊息,獲取下單訊息,進行庫操作。 
    就算庫存系統出現故障,訊息佇列也能保證訊息的可靠投遞,不會導致訊息丟失(馬雲這下高興了).

流量削峰

流量削峰一般在秒殺活動中應用廣泛 
場景:秒殺活動,一般會因為流量過大,導致應用掛掉,為了解決這個問題,一般在應用前端加入訊息佇列。 
作用: 
1.可以控制活動人數,超過此一定閥值的訂單直接丟棄(我為什麼秒殺一次都沒有成功過呢^^) 
2.可以緩解短時間的高流量壓垮應用(應用程式按自己的最大處理能力獲取訂單) 
這裡寫圖片描述
1.使用者的請求,伺服器收到之後,首先寫入訊息佇列,加入訊息佇列長度超過最大值,則直接拋棄使用者請求或跳轉到錯誤頁面. 
2.秒殺業務根據訊息佇列中的請求資訊,再做後續處理.

3.系統架構

這裡寫圖片描述
幾個概念說明: 
Broker:它提供一種傳輸服務,它的角色就是維護一條從生產者到消費者的路線,保證資料能按照指定的方式進行傳輸, 
Exchange:訊息交換機,它指定訊息按什麼規則,路由到哪個佇列。 
Queue:訊息的載體,每個訊息都會被投到一個或多個佇列。 
Binding:繫結,它的作用就是把exchange和queue按照路由規則繫結起來. 
Routing Key:路由關鍵字,exchange根據這個關鍵字進行訊息投遞。 
vhost:虛擬主機,一個broker裡可以有多個vhost,用作不同使用者的許可權分離。 
Producer:訊息生產者,就是投遞訊息的程式. 
Consumer:訊息消費者,就是接受訊息的程式. 
Channel:訊息通道,在客戶端的每個連線裡,可建立多個channel.

4.任務分發機制

4.1Round-robin dispathching迴圈分發

RabbbitMQ的分發機制非常適合擴充套件,而且它是專門為併發程式設計的,如果現在load加重,那麼只需要建立更多的Consumer來進行任務處理。

4.2Message acknowledgment訊息確認

為了保證資料不被丟失,RabbitMQ支援訊息確認機制,為了保證資料能被正確處理而不僅僅是被Consumer收到,那麼我們不能採用no-ack,而應該是在處理完資料之後傳送ack. 
在處理完資料之後傳送ack,就是告訴RabbitMQ資料已經被接收,處理完成,RabbitMQ可以安全的刪除它了.
如果Consumer退出了但是沒有傳送ack,那麼RabbitMQ就會把這個Message傳送到下一個Consumer,這樣就保證在Consumer異常退出情況下資料也不會丟失. 
RabbitMQ它沒有用到超時機制.RabbitMQ僅僅通過Consumer的連線中斷來確認該Message並沒有正確處理,也就是說RabbitMQ給了Consumer足夠長的時間做資料處理。 
如果忘記ack,那麼當Consumer退出時,Mesage會重新分發,然後RabbitMQ會佔用越來越多的記憶體.

5.Message durability訊息持久化

要持久化佇列queue的持久化需要在宣告時指定durable=True; 
這裡要注意,佇列的名字一定要是Broker中不存在的,不然不能改變此佇列的任何屬性. 
佇列和交換機有一個建立時候指定的標誌durable,durable的唯一含義就是具有這個標誌的佇列和交換機會在重啟之後重新建立,它不表示說在佇列中的訊息會在重啟後恢復 
訊息持久化包括3部分 
1. exchange持久化,在宣告時指定durable => true

hannel.ExchangeDeclare(ExchangeName, "direct", durable: true, autoDelete: false, arguments: null);//宣告訊息佇列,且為可持久化的11

2.queue持久化,在宣告時指定durable => true

channel.QueueDeclare(QueueName, durable: true, exclusive: false, autoDelete: false, arguments: null);//宣告訊息佇列,且為可持久化的11

3.訊息持久化,在投遞時指定delivery_mode => 2(1是非持久化).

channel.basicPublish("", queueName, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());  11

如果exchange和queue都是持久化的,那麼它們之間的binding也是持久化的,如果exchange和queue兩者之間有一個持久化,一個非持久化,則不允許建立繫結. 
注意:一旦建立了佇列和交換機,就不能修改其標誌了,例如,建立了一個non-durable的佇列,然後想把它改變成durable的,唯一的辦法就是刪除這個佇列然後重現建立。

6.Fair dispath 公平分發

你可能也注意到了,分發機制不是那麼優雅,預設狀態下,RabbitMQ將第n個Message分發給第n個Consumer。n是取餘後的,它不管Consumer是否還有unacked Message,只是按照這個預設的機制進行分發. 
那麼如果有個Consumer工作比較重,那麼就會導致有的Consumer基本沒事可做,有的Consumer卻毫無休息的機會,那麼,Rabbit是如何處理這種問題呢? 
這裡寫圖片描述 
通過basic.qos方法設定prefetch_count=1,這樣RabbitMQ就會使得每個Consumer在同一個時間點最多處理一個Message,換句話說,在接收到該Consumer的ack前,它不會將新的Message分發給它

channel.basic_qos(prefetch_count=1) 11

注意,這種方法可能會導致queue滿。當然,這種情況下你可能需要新增更多的Consumer,或者建立更多的virtualHost來細化你的設計。

7.分發到多個Consumer

7.1Exchange

先來溫習以下交換機路由的幾種型別: 
Direct Exchange:直接匹配,通過Exchange名稱+RountingKey來傳送與接收訊息. 
Fanout Exchange:廣播訂閱,向所有的消費者釋出訊息,但是隻有消費者將佇列繫結到該路由器才能收到訊息,忽略Routing Key. 
Topic Exchange:主題匹配訂閱,這裡的主題指的是RoutingKey,RoutingKey可以採用萬用字元,如:*或#,RoutingKey命名採用.來分隔多個詞,只有訊息這將佇列繫結到該路由器且指定RoutingKey符合匹配規則時才能收到訊息; 
Headers Exchange:訊息頭訂閱,訊息釋出前,為訊息定義一個或多個鍵值對的訊息頭,然後消費者接收訊息同時需要定義類似的鍵值對請求頭:(如:x-mactch=all或者x_match=any),只有請求頭與訊息頭匹配,才能接收訊息,忽略RoutingKey. 
預設的exchange:如果用空字串去宣告一個exchange,那麼系統就會使用”amq.direct”這個exchange,我們建立一個queue時,預設的都會有一個和新建queue同名的routingKey繫結到這個預設的exchange上去

channel.BasicPublish("", "TaskQueue", properties, bytes);11

因為在第一個引數選擇了預設的exchange,而我們申明的佇列叫TaskQueue,所以預設的,它在新建一個也叫TaskQueue的routingKey,並繫結在預設的exchange上,導致了我們可以在第二個引數routingKey中寫TaskQueue,這樣它就會找到定義的同名的queue,並把訊息放進去。 
如果有兩個接收程式都是用了同一個的queue和相同的routingKey去繫結direct exchange的話,分發的行為是負載均衡的,也就是說第一個是程式1收到,第二個是程式2收到,以此類推。 
如果有兩個接收程式用了各自的queue,但使用相同的routingKey去繫結direct exchange的話,分發的行為是複製的,也就是說每個程式都會收到這個訊息的副本。行為相當於fanout型別的exchange。 
下面詳細來說:

7.2 Bindings 繫結

繫結其實就是關聯了exchange和queue,或者這麼說:queue對exchange的內容感興趣,exchange要把它的Message deliver到queue。

7.3Direct exchange

Driect exchange的路由演算法非常簡單:通過bindingkey的完全匹配,可以用下圖來說明. 
這裡寫圖片描述
Exchange和兩個佇列繫結在一起,Q1的bindingkey是orange,Q2的binding key是black和green. 
當Producer publish key是orange時,exchange會把它放到Q1上,如果是black或green就會到Q2上,其餘的Message被丟棄.

7.4 Multiple bindings

多個queue繫結同一個key也是可以的,對於下圖的例子,Q1和Q2都繫結了black,對於routing key是black的Message,會被deliver到Q1和Q2,其餘的Message都會被丟棄. 
這裡寫圖片描述

7.5 Topic exchange

對於Message的routing_key是有限制的,不能使任意的。格式是以點號“.”分割的字元表。比如:”stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”。你可以放任意的key在routing_key中,當然最長不能超過255 bytes。 
對於routing_key,有兩個特殊字元

  • *(星號)代表任意一個單詞

  • #(hash)0個或多個單詞 
    這裡寫圖片描述
    Producer傳送訊息時需要設定routing_key,routing_key包含三個單詞和連個點號o,第一個key描述了celerity(靈巧),第二個是color(色彩),第三個是物種: 
    在這裡我們建立了兩個繫結: Q1 的binding key 是”.orange.“; Q2 是 “..rabbit” 和 “lazy.#”:

    • Q1感興趣所有orange顏色的動物

    • Q2感興趣所有rabbits和所有的lazy的. 
      例子:rounting_key 為 “quick.orange.rabbit”將會傳送到Q1和Q2中 
      rounting_key 為”lazy.orange.rabbit.hujj.ddd”會被投遞到Q2中,#匹配0個或多個單詞。

8.訊息序列化

RabbitMQ使用ProtoBuf序列化訊息,它可作為RabbitMQ的Message的資料格式進行傳輸,由於是結構化的資料,這樣就極大的方便了Consumer的資料高效處理,當然也可以使用XML,與XML相比,ProtoBuf有以下優勢: 
1.簡單 
2.size小了3-10倍 
3.速度快了20-100倍 
4.易於程式設計 
6.減少了語義的歧義. 
,ProtoBuf具有速度和空間的優勢,使得它現在應用非常廣泛











本文轉自 huangzp168 51CTO部落格,原文連結:http://blog.51cto.com/huangzp/1946058,如需轉載請自行聯絡原作者

相關文章