Redis--黑馬點評--基于stream消息隊列的秒殺優化業務詳解

基于redis的stream結構作為消息隊列,實現異步秒殺下單

需求:

  • 創建一個Stream類型的消息隊列,名為stream.oreders

  • 修改之前的秒殺下單Lua腳本,在認定有搶夠資格后,直接向stream.orders中添加消息,內容包括voucherId、userId、orderId

  • 項目啟動后,開啟一個線程任務,嘗試獲取stream.orders中的消息,完成下單

首先,使用命令行來創建消息隊列:

image-20250702162909923

其次,需要修改Lua腳本:

------ Generated by EmmyLua(https://github.com/EmmyLua)--- Created by 20893.--- DateTime: 2025/6/27 15:48----- 1.參數列表--1.1優惠券IDlocal voucherId = ARGV[1]--1.2用戶IDlocal userId = ARGV[2]-- 1.3 訂單IDlocal orderId = ARGV[3]--2.數據key--2.1庫存keylocal stockKey = 'seckill:stock:' .. voucherId--2.2訂單keylocal orderKey = 'seckill:order:' .. voucherId--3.業務邏輯--3.1判斷庫存是否充足-- redis.call('get',stockKey)中取出的值是String類型,需要將其轉化成int類型,調用tonumber()方法if  (tonumber(redis.call('get', stockKey))<= 0) then--3.2. 庫存不足 返回1return 1end--3.3判斷用戶是否重復下單if (redis.call('sismember', orderKey, userId) == 1) then--3.4. 重復下單 返回2return 2end--3.5扣減庫存redis.call('incrby', stockKey, -1)--3.6記錄訂單redis.call('sadd', orderKey, userId)--3.7發送消息到隊列中,xadd stream.orders * k1 v1 ...redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)return 0

還需要修改Java業務代碼:

?public Result seckillVoucher(Long voucherId) {//獲取用戶Long userId = UserHolder.getUser().getId();long orderId = redisIdWorker.nextId("order");//1.執行Lua腳本Long result = stringRedisTemplate.execute(SECKILL_SCRIPT, Collections.emptyList(),voucherId.toString(),userId.toString(),String.valueOf(orderId));//2.判斷結果是否為0int r = result.intValue();if (result != 0){//2.1.不為0,代表沒有購買資格return Result.fail(r == 1 ? "庫存不足" : "不能重復下單");}//獲取代理對象proxy = (IVoucherOrderService) AopContext.currentProxy();//3.返回訂單IDreturn Result.ok(orderId);

最終,根據我們的偽代碼進行對異步線程中業務流程的修改

while(true){//嘗試今天隊列,使用阻塞模式,最長等待2000毫秒Object msg = redis.call("xreadgroup group g1 c1 count 1 block 200 streams s1 >");if(msg == null){//null說明沒有消息,繼續下一次continue;}try{//處理消息,完成后需要確認消息(ACK)handleMessage(msg);}catch(Exception e){while(true){Object msg = redis.call("xreadgroup group g1 c1 count 1 block 200 streams s1 0");if(msg == null){//null說明沒有異常消息,所有消息已確認,結束循環break;}try{//說明有異常消息,再次處理handleMessage(msg);}catch(Exception e){//再次出現異常,記錄日志,繼續循環continue;}}}}

代碼展示:

?private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();//該注解表示在類初始化之后執行@PostConstructprivate void init(){SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());}private ?class VoucherOrderHandler implements Runnable{String queueName = "stream.orders";@Overridepublic void run() {while ( true){try {//獲取消息隊列中的訂單信息 xreadgroup group g1 c1 count 1 block 2000 streams stream.orders >List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create(queueName, ReadOffset.lastConsumed()));//判斷消息獲取是否成功if(list == null || list.isEmpty()){//2.1如果獲取失敗,說明沒有消息,繼續下一次循環continue;}//2.2.解析消息中的數據MapRecord<String, Object, Object> entries = list.get(0);Map<Object, Object> value = entries.getValue();//將map對象轉換成訂單對象VoucherOrder order = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);//2.2.如果獲取成功,可以下單handleVoucherOrder(order);//3.ACK確認 sack stream.order g1 idstringRedisTemplate.opsForStream().acknowledge(queueName,"g1",entries.getId());?} catch (Exception e) {log.error("獲取訂單信息異常",e);handlePendingList();}}?}?private void handlePendingList() {while( true){try {//獲取pending-list中的訂單信息 xreadgroup group g1 c1 count 1 streams stream.orders 0List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)),StreamOffset.create(queueName, ReadOffset.from("0")));//判斷消息獲取是否成功if(list == null || list.isEmpty()){//2.1如果獲取失敗,說明pending-list沒有消息,繼續下一次循環break;}//2.2.解析消息中的數據MapRecord<String, Object, Object> entries = list.get(0);Map<Object, Object> value = entries.getValue();//將map對象轉換成訂單對象VoucherOrder order = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);//2.2.如果獲取成功,可以下單handleVoucherOrder(order);//3.ACK確認 sack stream.order g1 idstringRedisTemplate.opsForStream().acknowledge(queueName,"g1",entries.getId());?} catch (Exception e) {log.error("獲取pending-list訂單信息異常",e);//如果害怕因為報錯導致陷入循環,可以設置休眠時間try {Thread.sleep(20);} catch (InterruptedException ex) {throw new RuntimeException(ex);}}}}}

整體業務流程描述:

首先嘗試從消息隊列中讀取數據,如果數據獲取失敗,直接進行下一次循環,再來讀一次消息隊列,如果獲取成功,說明有訂單信息需要處理,就去解析訂單信息,完成下單,在進行ack確認。在進行下一次讀取,繼續循環,如果在處理消息的過程拋出異常,導致該消息沒有確認,那么該消息就會進入pending-list,就被catch到,在catch中執行handlePendinglist()函數,在該函數中,首先去pending-list獲取未確認消息,如果讀到,則解析消息,處理,下單,ack確認,如果沒有異常消息,就會直接跳出循環,異常處理結束,如果再拋異常,就再度循環。直到pending-list中所有異常全部處理完成為止。

進行測試:

image-20250702173402911

image-20250702173423837

image-20250702173452042

image-20250702173524978

再次測試:

image-20250702173728568

至此優化秒殺下單的業務需求完成。

希望對大家有所幫助。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/87693.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/87693.shtml
英文地址,請注明出處:http://en.pswp.cn/web/87693.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Zephyr RTOS 防止中斷影響數據寫入

目錄 概述 1 中斷保護核心策略 1.1 中斷鎖定/解鎖 (IRQ Locking) 1.2 自旋鎖 (Spin Locks) 2 高級保護技術 2.1 雙重緩沖技術 2.2 RCU (Read-Copy-Update) 模式 3 中斷安全數據寫入模式 3.1 FIFO隊列保護 3.2 原子操作保護 4 性能優化策略 4.1 分區數據保護 4.2 中斷…

Hinge×亞矩云手機:以“深度連接”為名,重構云端社交的“真實感”

當傳統婚戀社交應用困于“淺層匹配”“硬件性能瓶頸”與“信任成本高企”&#xff0c;當Z世代對“靈魂共鳴、沉浸體驗、隱私安全”的需求愈發迫切&#xff0c;以“設計讓你刪除的應用”為理念的Hinge&#xff0c;正攜手亞矩云手機開啟一場“云端深度社交革命”——用云端算力破…

OpenSSL 內存泄漏修復全景:119 個歷史 Commit 的類型分析與防御啟示

1 前言 openssl 開源庫作為 C/C 項目中常用的組件庫&#xff0c;截至 2025年7月4日 &#xff0c;openssl 的提交記錄包含 119 個 Fix memory leak 。 本文基于源碼 Commit 分析&#xff0c;揭示了 OpenSSL 內存泄漏修復從被動應對到主動防御的演進趨勢&#xff0c;給各位 C/C…

十一、Python 3.13 的新特性和更新內容

1. 性能提升 1.1 解釋器性能優化 更快的啟動速度&#xff1a;Python 3.13 啟動時間比 3.12 快約 10-15%。內存使用優化&#xff1a;減少了內存占用&#xff0c;特別是在處理大型數據結構時。 1.2 字節碼優化 新的字節碼指令&#xff1a;引入了更高效的字節碼指令&#xff0…

后端 Maven打包 JAR 文件、前端打包dist文件、通過后端服務訪問前端頁面、Nginx安裝與部署

打包 JAR 文件通常使用 Maven 或 Gradle 構建工具&#xff08;Spring Boot 項目默認推薦 Maven&#xff09;。以下是詳細步驟和常見問題解答&#xff1a; 一、后端 Maven打包 JAR 文件 1. 確保項目是 Spring Boot 項目 項目結構應包含 pom.xml&#xff08;Maven 配置文件&am…

大數據系列 | 日志數據采集工具Filebeat的架構分析及應用

大數據系列 | 日志數據采集工具Filebeat的架構分析及應用 1. Filebeat的由來2. Filebeat原理架構分析3. Filebeat的應用3.1. 安裝Filebeat3.2. 實戰采集應用程序日志1. Filebeat的由來 在介紹Filebeat之前,先介紹一下Beats。Beats是一個家族的統稱,Beats家族有8個成員,早期的…

基于 Vue + RuoYi 架構設計的商城Web/小程序實訓課程

以下是基于 Vue RuoYi 架構設計的商城Web/小程序實訓課程方案&#xff0c;結合企業級開發需求與教學實踐&#xff0c;涵蓋全棧技術棧與實戰模塊&#xff1a; &#x1f4da; 一、課程概述 目標&#xff1a;通過Vue前端 RuoYi后端&#xff08;Spring Boot&#xff09;開發企業…

Puppeteer 相關漏洞-- Google 2025 Sourceless

題目的代碼非常簡單,核心只有這一句 page.goto(url, { timeout: 2000 });方案1 Puppeteer 是一個常用的自動化瀏覽器工具&#xff0c;默認支持 Chrome&#xff0c;但也可以配置支持 Firefox。然而&#xff0c;當 Puppeteer 運行在 Firefox 上時&#xff0c;會自動關閉一些安全特…

LucidShape 2024.09 最新

LucidShape的最新版本2024.09帶來了一系列新功能與增強功能&#xff0c;旨在解決光學開發者面臨的最常見和最復雜的挑戰。從微透鏡陣列&#xff08;MLA&#xff09;的自動掩模計算&#xff0c;到高級分析功能的改進&#xff0c;LucidShape 2024.09致力于簡化工作流程并增強設計…

mini-electron使用方法

把在官方群里“官方132版”目錄里下載的包里的minielectron_x64.exe解壓到你本地某個目錄&#xff0c;改名成electron.exe&#xff0c;比如G:\test\ele_test\mini_electron_pack\electron.exe。 修改你項目的package.json文件。一個例子是&#xff1a; {"name": &q…

Android 網絡全棧攻略(七)—— 從 OkHttp 攔截器來看 HTTP 協議二

Android 網絡全棧攻略系列文章&#xff1a; Android 網絡全棧攻略&#xff08;一&#xff09;—— HTTP 協議基礎 Android 網絡全棧攻略&#xff08;二&#xff09;—— 編碼、加密、哈希、序列化與字符集 Android 網絡全棧攻略&#xff08;三&#xff09;—— 登錄與授權 Andr…

45-使用scale實現圖形縮放

45-使用scale實現圖形縮放_嗶哩嗶哩_bilibili45-使用scale實現圖形縮放是一次性學會 Canvas 動畫繪圖&#xff08;核心精講50個案例&#xff09;2023最新教程的第46集視頻&#xff0c;該合集共計53集&#xff0c;視頻收藏或關注UP主&#xff0c;及時了解更多相關視頻內容。http…

軟件開發早期階段,使用存儲過程的優勢探討:敏捷開發下的利器

在現代軟件開發中&#xff0c;隨著持續集成與敏捷開發的深入推進&#xff0c;開發團隊越來越重視快速響應需求變更、快速上線迭代。在這種背景下&#xff0c;傳統將業務邏輯全部放在應用層的方式在某些階段顯得笨重。本文將探討在軟件開發初期&#xff0c;特別是在需求尚不穩定…

『 C++入門到放棄 』- string

C 學習筆記 - string 一、什麼是string ? string 是 C 中標準函數庫中的一個類&#xff0c;其包含在 中 該類封裝了C語言中字符串操作&#xff0c;提供內存管理自動化與更多的操作 支持複製、比較、插入、刪除、查找等功能 二、常用接口整理 類別常用方法 / 說明建立與指…

ARM架構下C++程序堆溢出與棧堆碰撞問題深度解析

ARM架構下C程序堆溢出與棧堆碰撞問題深度解析 一、問題背景&#xff1a;從崩潰現象到內存異常 在嵌入式系統開發中&#xff0c;程序崩潰是常見但棘手的問題。特別是在ARM架構設備上&#xff0c;一種典型的崩潰場景如下&#xff1a;程序在執行聚類算法或大規模數據處理時突然終…

.NET9 實現排序算法(MergeSortTest 和 QuickSortTest)性能測試

在 .NET 9 平臺下&#xff0c;我們對兩種經典的排序算法 MergeSortTest&#xff08;歸并排序&#xff09;和 QuickSortTest&#xff08;快速排序&#xff09;進行了性能基準測試&#xff08;Benchmark&#xff09;&#xff0c;以評估它們在不同數據規模下的執行效率、內存分配及…

RabbitMQ - SpringAMQP及Work模型

一、概述RabbitMQ是一個流行的開源消息代理&#xff0c;支持多種消息傳遞協議。它通常用于實現異步通信、解耦系統組件和分布式任務處理。Spring AMQP是Spring框架下的一個子項目&#xff0c;提供了對RabbitMQ的便捷訪問和操作。本文將詳細介紹RabbitMQ的工作模型&#xff08;W…

微信小程序51~60

1.界面交互-loading提示框 loading提示框用于增加用戶體驗&#xff0c; 對應的API有兩個&#xff1a; wx.showLoading()顯示loading提示框wx.hideLoading()關閉loading提示框 Page({getData () {//顯示loading提示框wx.showLoading({//提示內容不會自動換行&#xff0c;多出來的…

SqueezeBERT:計算機視覺能為自然語言處理在高效神經網絡方面帶來哪些啟示?

摘要 人類每天閱讀和撰寫數千億條消息。得益于大規模數據集、高性能計算系統和更優的神經網絡模型&#xff0c;自然語言處理&#xff08;NLP&#xff09;技術在理解、校對和組織這些消息方面取得了顯著進展。因此&#xff0c;將 NLP 部署于各類應用中&#xff0c;以幫助網頁用…

Springboot開發常見注解一覽

注解用法常用參數Configuration用于標記類為配置類&#xff0c;其中通過Bean方法定義Spring管理的組件。它替代XML配置&#xff0c;用Java代碼聲明對象創建邏輯&#xff0c;并確保單例等容器特性生效。相當于給Spring提供一個“制造說明書”來組裝應用部件RestControllerRestCo…