1. 程式人生 > >SpringBoot整合RabbitMQ之典型應用場景實戰二

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

factor aid 分享圖片 actor esp rem 排隊 stc tps

實戰前言
RabbitMQ 作為目前應用相當廣泛的消息中間件,在企業級應用、微服務應用中充當著重要的角色。特別是在一些典型的應用場景以及業務模塊中具有重要的作用,比如業務服務模塊解耦、異步通信、高並發限流、超時業務、數據延遲處理等。上篇博文我介紹分享了RabbitMQ在業務服務模塊異步解耦以及通信的實戰業務場景,感興趣童鞋可以前往觀看:http://blog.51cto.com/13877966/2297056
這邊博文我們繼續介紹分享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微服務項目的實戰,另外也介紹了消息確認機制的配置實戰跟並發量配置,下篇博文將繼續分享死信隊列的相關內容及其實戰,相關源碼數據庫可以來這裏下載

https://pan.baidu.com/s/1KUuz_eeFXOKF3XRMY2Jcew

學習過程有任何問題均可以與我交流,QQ:1974544863!感興趣的童鞋可以關註一下我的微信公眾號!
技術分享圖片

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