
每年的雙十一期間,各大電商平臺流量暴增,同時,電商平臺系統的負載壓力也會很大,譬如訂單支付的場景,每個訂單支付成功后,服務器可能要完成扣級訓分、扣減優惠券、扣減商品庫存、發短信等一系列操作,單個用戶請求,服務器處理起來并沒有什么問題,但是,瞬時并發的多個請求到了服務器,資料庫壓力上來了,導致請求回應慢,甚至宕機,
為了解決這個問題,我們可能會想到,讓資料庫處理完一個請求后再處理下一個請求不就好了么,就這樣,訊息佇列出來了,訊息佇列,又稱為MQ(Message Queue),它實作了讓多個請求以訊息的形式排好隊,讓訊息處理程式一個一個的處理,有效防止了高并發給服務器帶來的壓力,
訊息佇列應用場景
MQ典型的應用場景有異步、削峰、解耦三種,
異步
譬如說一個系統A,它有一個操作處理完自己的邏輯以后需要呼叫其他系統的介面,如下圖:

這時候,代碼是這樣:
public class SystemA {
@Resource
SystemBapi systemBapi;
@Resource
SystemCapi systemCapi;
@Resource
SystemDapi systemDapi;
public void doSomething() {
//產生一個id
long id = doSomethingAction();
//呼叫其他系統介面
systemBapi.doSomething(id);
systemCapi.doSomething(id);
systemDapi.doSomething(id);
}
}
上面的代碼,系統A產生id的邏輯需要50ms,呼叫系統B的介面需要300ms,呼叫系統C的介面需要300ms,呼叫系統D的介面需要300ms,一個這樣的操作就需要50+300+300+300=950ms,如果后面還要對接其他系統,這個操作的時間會更長,
如果呼叫其他系統介面實時性要求不高,那么,為了提高用戶體驗和吞吐量,呼叫其他系統介面的操作就可以交給MQ實作異步操作,如下圖:

系統A執行完了以后,將id給到訊息佇列中,然后就直接回傳了,
削峰
譬如有3臺服務器組成集群,每臺服務器的處理能力是1000QPS,合起來就是3000QPS,遇上了流量高峰,達到了5000QPS,并發請求數量已經超過所有服務器總的處理能力,這時候就可以考慮利用MQ來控制并發數,以免服務器崩潰,
具體做法是所有請求先進入到MQ,然后每個服務器根據自己的能夠處理的請求數去消費訊息,也就是無論每秒多少個請求,系統只處理能力范圍內的請求數,剩下的請求等有資源再去處理,這就是“削峰填谷”,如下圖:

解耦
解耦就是降低了訊息生產者與消費者的耦合度,耦合度高,程式維護起來就會很麻煩,譬如,系統A產生了一個id后,需要把id交給系統B、系統C、系統D去處理,如果由系統A直接去呼叫其他系統介面,系統A的程式代碼需要寫上呼叫系統B、系統C、系統D介面的代碼,如果某一天系統C說不需要處理系統A的id了,讓系統A不要呼叫系統C的介面,那系統A要改代碼,又某一天系統E說我要處理系統A的id,讓系統A呼叫系統E的介面,系統A又得改代碼,系統A的程式員這樣子搞煩不煩?
系統A程式員有一天開竅了,把程式里所有呼叫外部系統的代碼都屏蔽,弄了個MQ中間件,讓系統A產生id以后就給到MQ,然后發個公告告訴所有其他系統的程式員,你們誰想要我這邊產生的id你們自己去MQ拿,別來煩我,這樣一來,系統A跟其他系統就解耦了,代碼也不用改來改去,

訊息佇列要注意的問題
問題一:可用性
MQ作為整個分布式架構的重要部件,如果MQ服務不可用,那整個系統都掛了,因此,MQ必須要支持集群,當下主流的MQ中間件都能夠不同程度的支持集群,實作了MQ服務的高可用,
問題二:訊息丟失
訊息丟失有可能發生在生產者丟失訊息、MQ本身丟失訊息、消費者丟失訊息3個方面,
-
生產者丟失訊息
生產者丟失訊息一般是在發送訊息的時候出現例外(譬如網路例外),導致MQ無法接收到訊息,這個問題可以采用本地訊息表+回呼通知+定時任務的方式解決,
?
就以系統A發送訊息,系統B消費訊息為例,具體解決方案如下:
1、系統A執行本地事務業務邏輯,并且往本地訊息表插入一條資料(代表準備要發送的訊息),訊息狀態為“未發送”,本地事務成功,提交保存本地資料,失敗則回滾,
2、本地事務成功后,發送訊息給MQ,
3、MQ接收到訊息后,回呼通知系統A,系統A把本地訊息表對應的訊息記錄狀態變為“已發送”,
4、定時任務輪詢本地訊息表,超過一定時間狀態為“未發送”的訊息重新發送給MQ,
5、定時任務處理超過一定次數一直發送不成功的訊息告警,人工介入,
? -
MQ丟失訊息
訊息成功發送到MQ,是先放到記憶體里的,如果還沒來得及給消費者消費訊息,MQ服務就掛了,就會丟失訊息,MQ服務集群可以一定程度上解決這個問題,但集群中各節點的資料同步也需要一定時間,如果在同步資料之前MQ服務就掛了,訊息也會丟失,還有一個方法就是MQ接收到訊息的同時,把訊息資料持久化到磁盤,這樣,MQ服務恢復的時候就可以從磁盤獲取資料重新給消費者消費,可能有人會問,那訊息還沒來得及持久化到磁盤MQ服務就掛了咋辦?如果是這樣,就可以用到前面說到的本地訊息表,把本地訊息表里的資料重新發一遍,
? -
消費者丟失訊息
消費者從MQ拉取訊息,還沒來得及處理訊息,消費者服務器掛了,此時,可能造成消費者丟失訊息,這種情況,可以讓消費者處理完訊息時給MQ一個確認訊息來解決,如果MQ沒有收到確認訊息,就會有重試的機制,最終確保訊息給到消費者消費,當然了,如果重試超過一定次數,就應該告警,人工介入,
問題三:重復消費
因為在網路延遲的情況下,訊息重復發送的問題不可避免的發生,譬如,生產者發送訊息的時候使用了重試機制,發送訊息后由于網路原因沒有收到MQ的確認資訊,然后又去重新發送了一次訊息,但其實MQ已經接到了訊息,并回傳了回應,只是因為網路原因導致生產者沒有收到MQ的確認資訊,這種情況下,生產者的訊息重試機制就會繼續就這個訊息重新發送,從而導致同一條訊息多次發送,這樣消費者也會重復消費這條訊息,當然,這只是列舉了一種情況,實際上還有其他情況會導致訊息被重復消費,
解決重復消費的關鍵就是在消費者端引入冪等性機制,什么是冪等性機制呢?我們可以把它理解成,假如一個介面被重復呼叫,依然可以保證資料的準確性,舉個例子,比如每條訊息都會有一個唯一的id,消費者處理完這個訊息會存盤這個id,如果處理訊息之前能找到這個id,就說明這條訊息已經處理過了,就不做處理并且回傳給MQ一個確認資訊,
訊息佇列中間件
為什么要用訊息佇列中間件?自己寫不行嗎?我們之所以要用中間件,是因為這些中間件已經解決了很多訊息佇列常見的問題(高可用、訊息丟失、重復消費......),而且各種中間件都有各自的特性,已經做得非常成熟了,你確定你寫的有這些中間件好用嗎?
目前在市面上比較主流的MQ中間件主要有,ActiveMQ、RabbitMQ、Kafka、RocketMQ 等這幾種,網上找來這幾個中間件的對比,如下表:
| 特性 | ActiveMQ | RabbitMQ | Kafka | RocketMQ |
|---|---|---|---|---|
| 所屬社區/公司 | Apache | Mozilla Public License | Apache | Apache/Ali |
| 單機呑吐量 | 萬級(最差) | 萬級 | 十萬級 | 十萬級(最高) |
| 時效性 | 毫秒級 | 微秒級 | 毫秒級 | 毫秒級 |
| 可用性 | 高(主從) | 高(主從) | 非常高(分布式) | 非常高(分布式) |
| 功能特性 | MQ領域功能極其完備 | 基于erlang開發,所以并發能力很強,性能極其好,延時很低 | 功能較為簡單,主要支持簡單的MQ功能,在大資料領域的實時計算以及日志采集被大規模使用 | MQ功能比較完備,擴展性佳 |
| 訊息可靠性 | 有較低的概率丟失資料 | 基本不丟 | 經過引數優化配置,可以做到 0 丟失 | 同 Kafka |
| 事務 | 支持 | 不支持 | 支持 | 支持 |
| broker端訊息過濾 | 支持 | 不支持 | 不支持 | 可以支持Tag標簽過濾和SQL運算式過濾 |
| 訊息查詢 | 支持 | 根據訊息id查詢 | 不支持 | 支持Message id或Key查詢 |
| 訊息回溯 | 支持 | 不支持 | 理論上可以支持時間或offset回溯,但是得修改代碼, | 支持按時間來回溯訊息,精度毫秒,例如從一天之前的某時某分某秒開始重新消費訊息, |
| 路由邏輯 | 基于交換機,可配置復雜路由邏輯 | 根據topic | 根據topic,可以配置過濾消費 | |
| 持久化 | 記憶體、檔案、資料庫 | 佇列基于記憶體,只能少量堆積 | 磁盤,大量堆積 | 磁盤,大量堆積 |
| 順序訊息 | 支持 | 不支持 | 支持 | 支持 |
| 社區活躍度 | 低 | 中 | 高 | 高 |
| 適用場景 | 主要場景就是解耦和異步呼叫,較少在大規模吞吐的場景中使用 | 資料量沒有那么大,小公司 | 一般配合大資料類的系統來進行實時資料計算、日志采集等場景, | 目前在阿里被廣泛應用在訂單、交易、充值、流計算、訊息推送、日志流式處理、binglog分發訊息等場景, |
根據上表,我個人認為對性能要求比較高的,推薦選擇RocketMQ,畢竟經歷了多年阿里雙十一極端并發的場景,如果是大資料領域的,可以選擇Kafka,
本文來自博客園,作者:Yi00,轉載請注明原文鏈接:https://www.cnblogs.com/ayic/p/16886475.html
聊聊技術,聊聊人生,歡迎關注我的公眾號!^_^
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/532585.html
標籤:其他
