SpringBoot整合RabbitMQ之典型應用場景實戰二

實戰前言
RabbitMQ 做爲目前應用至關普遍的消息中間件,在企業級應用、微服務應用中充當着重要的角色。特別是在一些典型的應用場景以及業務模塊中具備重要的做用,好比業務服務模塊解耦、異步通訊、高併發限流、超時業務、數據延遲處理等。上篇博文我介紹分享了RabbitMQ在業務服務模塊異步解耦以及通訊的實戰業務場景,感興趣童鞋能夠前往觀看:https://www.roncoo.com/article/detail/134309java

這邊博文咱們繼續介紹分享RabbitMQ消息確認機制以及併發量的配置,並介紹分享其在高併發系統場景下的實戰!數據庫

RabbitMQ 實戰:併發量配置與消息確認機制後端

實戰背景緩存

對於消息模型中的 listener 而言,默認狀況下是「單消費實例」的配置,即「一個 listener 對應一個消費者」,這種配置對於上面所講的「異步記錄用戶操做日誌」、「異步發送郵件」等併發量不高的場景下是適用的。可是在對於秒殺系統、商城搶單等場景下可能會顯得很吃力!服務器

咱們都知道,秒殺系統跟商城搶單均有一個共同的明顯的特徵,即在某個時刻會有成百上千萬的請求到達咱們的接口,即瞬間這股巨大的流量將涌入咱們的系統,咱們能夠採用下面一圖來大體體現這一現象:多線程

當到了「開始秒殺」、「開始搶單」的時刻,此時系統可能會出現這樣的幾種現象:併發

  • 應用系統配置承載不了這股瞬間流量,致使系統直接掛掉,即傳說中的「宕機」現象;app

  • 接口邏輯沒有考慮併發狀況,數據庫讀寫鎖發生衝突,致使最終處理結果跟理論上的結果數據不一致(如商品存庫量只有 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微服務項目的實戰,另外也介紹了消息確認機制的配置實戰跟併發量配置,下篇博文將繼續分享死信隊列的相關內容及其實戰。另外,博主已將RabbitMQ相關技術以及場景實戰的相關要點錄製成了視頻教程,感興趣小夥伴能夠前往學習觀看:https://www.roncoo.com/course/view/95ffac8bd3aa4f2d8a0d83c32f46c69d

相關文章
相關標籤/搜索