使用RabbitMQ打造扛得住的高并發環境(三)
1. 前言
Hello,大家好。我們在上一小節中,介紹了使用 RabbitMQ 打造扛得住的高并發環境系列內容的第二小節部分,也就是我們此系列小節內容的基礎核心內容。
本小節會繼續介紹使用 RabbitMQ 打造扛得住的高并發環境系列內容的第三部分,在本小節中,我們會使用我們在第二節中使用 RabbitMQ 和 Redis 打造的高可用消息隊列去實現我們的一個真實的高并發的業務場景。
本小節會首先介紹一種老師在自己的實際工作中,出現的真實的高并發業務場景,并且,老師在應對這種高并發業務場景時,采用的基本解決方案就是采用的 RabbitMQ 和 Redis 共同改造的高可用消息隊列這種形式,還在等什么,讓我們先睹為快吧!
本節主要內容:
-
實際高并發業務場景概述;
-
實際高并發業務場景實現;
-
實際高并發業務場景測試。
2.實際高并發業務場景概述
本部分內容,老師會詳細介紹本套課程最終需要實現的一種業務場景,此種業務場景是老師自己在實際工作中遇到的真實的業務場景,同學們一定要先對這種業務場景有個清晰地了解之后,在繼續學習本小節后續地內容,如果你對這種業務場景沒有充分地了解,那么后面的實現思路你將不會看懂,這點同學們注意。
本業務場景實際上并不算復雜,我們每個同學在真實的日常生活中,或多或少都會接觸到,只不過平時同學們可能不會注意觀察或者思考。這種業務場景有一個專有的代名詞,相信大家都已經聽說過了,那就是’秒殺’業務場景。
那么,什么是秒殺業務場景呢?
這個秒殺的業務場景,出現在銷售行業的居多,比如日常生活中,我們在超市中去購買一種商品,這種商品的價格要比往常的價格要低很多,但是,這種商品的庫存數量是有限的,當我們購買這種商品時,必須要在一瞬間完成搶購這一動作。
隨著互聯網時代的快速發展,越來越多的線上電子商城已經出現在人們的日常生活中,以淘寶、京東為代表性的互聯網電子商城巨頭率先將這些線下的商品購買行為,轉換為線上的商品購買功能。
針對與上述這種秒殺搶購的業務場景,目前在各互聯網電子商城巨頭中都是有所體現的,比如我們熟知的雙十一活動,以及 618 商品大促活動,這些都是秒殺搶購業務場景的典型代表,那么,這種業務場景在線上又是如何實現的呢?
在分析一秒殺搶購業務場景的一個完整的線上業務流程是什么樣的之前,我們先來看一下,一般地線上商品購買的一個完整的業務流程是什么樣的,如下圖所示:

首先,用戶在有這種秒殺搶購的實際需求之后,用戶首先會登錄我們的線上商城系統,在用戶成功登錄本系統之后,用戶需要到我們線上商城系統的秒殺搶購專區,用戶可以在這個秒殺搶購專區中看到本商城系統中參與秒殺搶購活動的商品,這一過程我們稱為用戶挑選商品階段。
用戶在挑選完自己所需要的商品之后,可以將所需的商品放入購物車中,也可以直接點擊下單按鈕,來迅速完成對某一具體商品的下單操作。
如果用戶是將商品放入購物車中,那么用戶只能進行一個批量下單的動作,即用戶前往自己的購物車中,選中商品之后,點擊下單按鈕,進行一個批量下單操作,這一過程我們稱為用戶預下單階段。
在用戶將訂單創建完畢之后,就需要用戶選擇對應的支付方式,來完成商品價格的支付動作,對于線上電子商城而言,用戶可以選擇不同廠家的掃碼支付功能來完成支付,這一過程我們稱為用戶支付階段。
在用戶對所購商品支付完成之后,我們需要將用戶的商品支付結果返回給用戶,告知用戶商品支付的狀態,是支付成功了,還是支付過程中遇到問題,導致支付失敗了,這一過程往往我們會采取輪詢的方式實現,這一過程我們稱為用戶支付狀態回調階段。
在用戶的支付狀態成功回調給用戶之后,一個完整的線上商品購買流程就結束了,至于后續地商品物流信息等其他信息就不屬于我們商品購買的流程了。
在清楚了一般地商品購買全流程之后,我們就不難理解秒殺搶購的業務場景流程了。
其實,秒殺搶購的業務場景流程和一般地商品購買流程是一模一樣地,只不過在用戶預下單階段,以及用戶支付階段,在同一時刻會有大量的用戶請求需要我們處理,這就是秒殺業務場景和一般地商品購買流程中最大的區別點,其他地方并沒有什么區別。
在本小節中,我們需要實現上述業務場景,并對核心的秒殺搶購業務場景中的用戶預下單階段,以及用戶支付階段,做好高并發場景下的處理。
Tips: 同學們一定要清楚地理解上述所介紹的業務流程,如果看一遍不理解,那就反復多看幾遍,直到自己理解了即可。
3.實際高并發業務場景實現
在了解了秒殺搶購的業務場景流程之后,接下來我們就需要實現這一業務場景了,那么,這種業務場景我們應該怎么用 RabbitMQ 和 Redis 去實現呢?
在使用 RabbitMQ 打造扛得住的高并發環境系列小節內容的第二小節中,我們使用 RabbitMQ 消息通信中間件和 Redis 緩存中間件,對 RabbitMQ 自身的消息隊列進行了改造,改造成了一種 Redis 承載的高可用的消息隊列,在本節,我們就會用到這一高可用的消息隊列。
在實現上述實際高并發業務場景時,由于篇幅原因,我們并不會從用戶登錄開始,逐步地去實現每一個過程,我們只實現在秒殺搶購業務場景中,最核心的部分,也就是,當我們在秒殺搶購商品區域,點擊立即購買這個秒殺按鈕時,我們后臺所需要應對高并發處理的內容。
讓我們來看看具體應該怎么設計實現吧。
3.1 初始化 Redis 緩存數據
當我們點擊立即購買這個秒殺按鈕時,我們首先會獲取到用戶所挑選的商品數據, 在獲取到這些商品數據之后,我們需要根據這些商品數據中起到唯一區分商品的這一屬性,去查詢我們對應商品的庫存是否充足。
查詢庫存這種操作,我們放在 Redis 緩存中進行存儲。即,當我們的后臺服務啟動時,或者是在一個其他的什么時機的時候,我們會將系統中參與秒殺搶購的所有商品數據,或者這些關鍵的商品數據,放入到我們的 Redis 緩存中,這些數據中間就包括商品的庫存數量,如下代碼所示:
代碼實現:
// 向 Redis 緩存中初始化存儲秒殺商品數據
redisUtil.set("shipping_seckill" + shipping.getId(), shipping);
代碼解釋:
我們使用 redisUtil 工具類的 set 方法,將參與秒殺搶購的商品數據 shipping ,在后臺服務初始化時,放入到 Redis 緩存中,以備后續使用。
3.2 Redis 預減商品庫存
在校驗商品庫存時,我們會只查詢 Redis 緩存中的商品數據, 查看商品庫存是否足夠支撐用戶的商品購買數量,如果庫存數量不足以支撐,則提示用戶商品庫存不足,預下單失敗;如果庫存數量充足,則提示用戶商品預下單成功,如下代碼所示:
代碼實現:
Shipping shipping = redisUtil.get("shipping_seckill" + shipping.getId());
Integer shippingStorage = shipping.getStorage();
if(shippingStorage >= userCurrNums) {
// 預減庫存,并提示用戶商品預下單成功
}
代碼解釋:
第 1 行,我們使用 redisUtil 工具類的 get 方法,來獲取到存儲于 Redis 緩存中的商品數據。
第 2 行,我們通過聲明一個 shippingStorage 變量,來進一步獲取到用戶所挑選商品的庫存信息。
第 3-5 行,我們通過將商品庫存 shippingStorage 與用戶所購買的商品數量 userCurrNums 做一個比較,來判斷當前商品的庫存是否充足,并將結果提示給用戶。
3.3 RabbitMQ 處理秒殺商品訂單
在商品預減庫存成功之后,我們需要根據實際的業務需求來生成商品的訂單,并將該訂單發送到我們的 RabbitMQ 消息隊列中去,如下代碼所示:
代碼實現:
Shipping shipping = redisUtil.get("shipping_seckill" + shipping.getId());
Integer shippingStorage = shipping.getStorage();
if(shippingStorage >= userCurrNums) {
// 預減庫存成功,開始處理商品訂單
rabbitTemplate.convertAndSend("seckill_order_ex", "seckill_order_key", order, message -> {
// 設置具體的消費配置參數
})
}
代碼解釋:
第 1-3 行,和上述代碼相同,不再贅述。
第 5 行,我們使用 rabbitTemplate 的 convertAndSend 方法,來設置秒殺商品訂單所需要的交換機,routing key ,并將秒殺商品訂單發送到我們的 RabbitMQ 中。
將秒殺商品訂單發送到我們的 RabbitMQ 中之后,我們還需要對消息配置一種監聽器,來監聽消息有沒有真正到達 RabbitMQ 的交換機中。(由于篇幅原因,配置監聽部分請參考《RabbitMQ消息確認與返回機制介紹》小節)。
3.4 消費者完成商品支付
配置好監聽之后,我們的消費者就需要將消息進行消費了。從 RabbitMQ 消息隊列中獲取到消息之后,會返回給我們一個消息 ack 或 nack 的應答結果,表明消費者是否成功獲取到了消息。
當返回 ack 確認應答結果時,表明消費者已經成功獲取到了消息,此時,我們應該根據消費者所獲取到的秒殺訂單數據,來生成對應場景的支付二維碼,以提示用戶掃碼來完成商品支付。
當用戶支付成功之后,我們需要根據用戶的支付結果,同步更新我們 Redis 緩存,以及數據庫中,用戶秒殺商品和秒殺訂單的商品庫存數據,以及訂單的狀態, 實現代碼如下所示:
代碼實現:
switch (order.getPayState()) {
case 0 :
this.updateSeckillOrderInfo(order.getShippingId(), 0);
// 下同
default:
break;
}
代碼解釋:
第 1 行,我們使用了 switch 語句,來獲取用戶秒殺訂單的支付狀態。
第 2-7 行,我們使用 case 語句,來對不同的用戶秒殺訂單的支付狀態做出不同的數據處理,比如,支付狀態為 0 時,表示用戶支付成功,那么此時我就需要調用 updateSeckillOrderInfo 方法來更新用戶秒殺訂單的支付狀態。
至此,我們已經基本實現了秒殺搶購業務場景中的核心內容,即用戶點擊立即購買之后,我們的后臺服務來處理高并發請求的業務場景。
4.實際高并發業務場景測試
那么,我們雖然基本實現了這一業務場景中的核心部分內容,但是,我們的這個秒殺搶購商品的接口可以同時支撐多大的 QPS 呢?
接下來,讓我們使用 JMeter 來做一個簡單的測試,來看一下我們采用 RabbitMQ 和 Redis 的方式之下,我們這個服務接口的 QPS 吞吐量是多少,測試結果如下圖所示:

Tips:
1. 關于 JMeter 我們不會做任何介紹,需要同同學們課下進行了解。
2. 由于測試環境的不同,使用 JMter 測試的結果也會不進相同,上下存在一定的誤差,這是合理的。
3. 關于 QPS 的概念介紹,也需要同學們自行了解,本套課程不會做任何介紹。
4. 測試部分并不是我們課程內容的重點,只不過是為了體現一種量化的指標,來供同學們查看,這點同學們要搞清楚。
5. 小結

本小節為同學們介紹了使用 RabbitMQ 打造扛得住的高并發環境的第三部分內容,其主要介紹了,如何使用 RabbitMQ 和 Redis 共同打造的高可用消息隊列,來實現我們真實的一種秒殺搶購的業務場景,希望同學們都能夠理解這部分內容。
隨著使用 RabbitMQ 打造扛得住的高并發環境系列小節內容的結束,本套課程的進度也接近了尾聲。通過對 RabbitMQ 消息通信中間件系統性地介紹,希望同學們可以將 RabbitMQ 真正地應用到我們的項目中去。
最后,感謝各位同學的持續關注,本套課程就到這里了,希望同學們可以持續支持與關注,江湖路遠,我們有緣再會!