RabbitMQ 作為目前應用相當廣泛的訊息中介軟體,在企業級應用、微服務應用中充當著重要的角色。特別是在一些典型的應用場景以及業務模組中具有重要的作用,比如業務服務模組解耦、非同步通訊、高併發限流、超時業務、資料延遲處理等。上篇博文我介紹分享了RabbitMQ在業務服務模組非同步解耦以及通訊的實戰業務場景,感興趣童鞋可以前往觀看:
https://juejin.im/post/5bbe0640518825573058444a
這邊博文我們繼續介紹分享RabbitMQ訊息確認機制以及併發量的配置,並介紹分享其在高併發系統場景下的實戰!
RabbitMQ 實戰:併發量配置與訊息確認機制
實戰背景
對於訊息模型中的 listener 而言,預設情況下是“單消費例項”的配置,即“一個 listener 對應一個消費者”,這種配置對於上面所講的“非同步記錄使用者操作日誌”、“非同步傳送郵件”等併發量不高的場景下是適用的。但是在對於秒殺系統、商城搶單等場景下可能會顯得很吃力!
我們都知道,秒殺系統跟商城搶單均有一個共同的明顯的特徵,即在某個時刻會有成百上千萬的請求到達我們的介面,即瞬間這股巨大的流量將湧入我們的系統,我們可以採用下面一圖來大致體現這一現象:
當到了“開始秒殺”、“開始搶單”的時刻,此時系統可能會出現這樣的幾種現象:
-
應用系統配置承載不了這股瞬間流量,導致系統直接掛掉,即傳說中的“當機”現象;
-
介面邏輯沒有考慮併發情況,資料庫讀寫鎖發生衝突,導致最終處理結果跟理論上的結果資料不一致(如商品存庫量只有 100,但是高併發情況下,實際表記錄的搶到的使用者記錄資料量卻遠遠大於 100);
- 應用佔據伺服器的資源直接飆高,如 CPU、記憶體、寬頻等瞬間直接飆升,導致同庫同表甚至可能同 host 的其他服務或者系統出現卡頓或者掛掉的現象;
於是乎,我們需要尋找解決方案!對於目前來講,網上均有諸多比較不錯的解決方案,在此我順便提一下我們的應用系統採用的常用解決方案,包括:
-
我們會將處理搶單的整體業務邏輯獨立、服務化並做叢集部署;
-
我們會將那股巨大的流量拒在系統的上層,即將其轉移至 MQ 而不直接湧入我們的介面,從而減少資料庫讀寫鎖衝突的發生以及由於介面邏輯的複雜出現執行緒堵塞而導致應用佔據伺服器資源飆升;
-
我們會將搶單業務所在系統的其他同資料來源甚至同表的業務拆分獨立出去服務化,並基於某種 RPC 協議走 HTTP 通訊進行資料互動、服務通訊等等;
- 採用分散式鎖解決同一時間同個手機號、同一時間同個 IP 刷單的現象;
下面,我們用 RabbitMQ 來實戰上述的第二點!即我們會在“請求” -> “處理搶單業務的介面” 中間架一層訊息中介軟體做“緩衝”、“緩壓”處理,如下圖所示:
併發量配置與訊息確認機制
正如上面所講的,對於搶單、秒殺等高併發系統而言,如果我們需要用 RabbitMQ 在 “請求” – “介面” 之間充當限流緩壓的角色,那便需要我們對 RabbitMQ 提出更高的要求,即支援高併發的配置,在這裡我們需要明確一點,“併發消費者”的配置其實是針對 listener 而言,當配置成功後,我們可以在 MQ 的後端控制檯應用看到 consumers 的數量,如下所示:
其中,這個 listener 在這裡有 10 個 consumer 例項的配置,每個 consumer 可以預監聽消費拉取的訊息數量為 5 個(如果同一時間處理不完,會將其快取在 mq 的客戶端等待處理!)
另外,對於某些訊息而言,我們有時候需要嚴格的知道訊息是否已經被 consumer 監聽消費處理了,即我們有一種訊息確認機制來保證我們的訊息是否已經真正的被消費處理。在 RabbitMQ 中,訊息確認處理機制有三種:Auto – 自動、Manual – 手動、None – 無需確認,而確認機制需要 listener 實現 ChannelAwareMessageListener 介面,並重寫其中的確認消費邏輯。在這裡我們將用 “手動確認” 的機制來實戰使用者商城搶單場景。
1.在 RabbitMQConfig 中配置確認消費機制以及併發量的配置
2.訊息模型的配置資訊
3.RabbitMQ 後端控制檯應用檢視此佇列的併發量配置
4.listener 確認消費處理邏輯:在這裡我們需要開發搶單的業務邏輯,即“只有當該商品的庫存 >0 時,搶單成功,扣減庫存量,並將該搶單的使用者資訊記錄入表,非同步通知使用者搶單成功!”
5.緊接著我們採用 CountDownLatch 模擬產生高併發時的多執行緒請求(或者採用 jmeter 實施壓測也可以!),每個請求將攜帶產生的隨機數:充當手機號 -> 充當訊息,最終入搶單佇列!在這裡,我模擬了 50000 個請求,相當於 50000 手機號同一時間發生搶單的請求,而設定的產品庫存量為 100,這在 product 資料庫表即可設定
6.將搶單請求的手機號資訊壓入佇列,等待排隊處理
7.在最後我們寫個 Junit 或者寫個 Controller,進行 initService.generateMultiThread();
呼叫模擬產生高併發的搶單請求即可
@RestController
public class ConcurrencyController {
private static final Logger log= LoggerFactory.getLogger(HelloWorldController.class);
private static final String Prefix="concurrency";
@Autowired
private InitService initService;
@RequestMapping(value = Prefix+"/robbing/thread",method = RequestMethod.GET)
public BaseResponse robbingThread(){
BaseResponse response=new BaseResponse(StatusCode.Success);
initService.generateMultiThread();
return response;
}}
複製程式碼
8.最後,我們當然是跑起來,在控制檯我們可以觀察到系統不斷的在產生新的請求(執行緒)– 相當於不斷的有搶單的手機號湧入我們的系統,然後入佇列,listener 監聽到請求之後消費處理搶單邏輯!最後我們可以觀察兩張資料庫表:商品庫存表、商品成功搶單的使用者記錄表 – 只有當庫存表中商品對應的庫存量為 0、商品成功搶單的使用者記錄剛好 100 時 即表示我們的實戰目的以及效果已經達到了!!
總結:如此一來,我們便將 request 轉移到我們的 mq,在一定程度緩解了我們的應用以及介面的壓力!當然,實際情況下,我們的配置可能遠遠不只程式碼層次上的配置,比如我們的 mq 可能會做叢集配置、負載均衡、商品庫存的更新可能會考慮分庫分表、庫存更新可能會考慮獨立為庫存 Dubbo 服務並通過 Rest Api 非同步通訊互動並獨立部署等等。這些優化以及改進的目的其實無非是為了能限流、緩壓、保證系統穩定、資料的一致等!而我們的 MQ,在其中可以起到不可磨滅的作用,其字如其名:“訊息佇列”,而佇列具有 “先進先出” 的特點,故而所有進入 MQ 的訊息都將 “乖巧” 的在 MQ 上排好隊,先來先排隊,先來先被處理消費,由此一來至少可以避免 “瞬間時刻一窩蜂的 request 湧入我們的介面” 的情況!
附註:在用 RabbitMQ 實戰上述高併發搶單解決方案,其實我也在資料庫層面進行了優化,即在讀寫存庫時採用了“類似樂觀鎖”的寫法,保證:搶單的請求到來時有庫存,更新存庫時保證有庫存可以被更新!
彩蛋:本博文繼續分享介紹了RabbitMQ典型應用業務場景的實戰-併發系統下RabbitMQ的限流作用以及基於SpringBoot微服務專案的實戰,另外也介紹了訊息確認機制的配置實戰跟併發量配置,下篇博文將繼續分享死信佇列的相關內容及其實戰,相關原始碼資料庫可以來這裡下載
學習過程有任何問題均可以與我交流,QQ:1974544863!感興趣的童鞋可以關注一下我的微信公眾號!(debug最近已經將RabbitMQ的實戰內容整理為一套視訊教程:SpringBoot整合RabbitMQ實戰,感興趣的童鞋可以加QQ諮詢進行技術交流!)