【redis實戰篇】第六天

摘要:

????????本文介紹了基于Redis的秒殺系統優化方案,主要包含兩部分:1)通過Lua腳本校驗用戶秒殺資格,結合Java異步處理訂單提升性能;2)使用Redis Stream實現消息隊列處理訂單。方案采用Lua腳本保證庫存校驗和一人一單的原子性,通過阻塞隊列異步保存訂單,并引入Redisson分布式鎖防止重復下單。

????????Redis Stream實現消息隊列,支持消費組和ACK確認機制,確保訂單可靠處理。系統還設計了pending-list異常處理機制,保證訂單處理的最終一致性。這種架構顯著提升了秒殺系統的高并發性能和數據一致性。

一,秒殺優化

redis校驗用戶秒殺資格(庫存是否充足且保證一人一單),通過阻塞隊列異步處理保存訂單到數據庫的操作,提升秒殺性能。

1,編寫校驗秒殺資格lua腳本

庫存不足返回1,用戶重復下單返回2,資格校驗通過返回0。

local voucherId = ARGV[1]
local userId = ARGV[2]local stockKey = 'seckill:stock:' .. voucherId
local orderKey = 'seckill:order:' .. voucherId
---庫存不足
if tonumber(redis.call('get', 'stockKey'))<=0 thenreturn 1
end
---庫存充足,判斷用戶是否下單
if redis.call('sismember', orderKey, userId) ==1 then---重復下單return 2
end
---扣減庫存保存用戶id到set中
redis.call('incrby', stockKey, -1)
redis.call('sadd', orderKey, userId)
return 0

2,讀取lua腳本到Java程序

(1)lua文件讀取配置

    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;static {SECKILL_SCRIPT = new DefaultRedisScript<>();//采用spring的讀取文件資源的ClassPathResourceSECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));SECKILL_SCRIPT.setResultType(Long.class);}

(2)調用stringRedisTemlate的execute方法讀取SECKILL_SCRIPT腳本,并傳入參數ARGV[..],因為腳本中不需要KEYS[..]變量,所以這里傳入空集合。

Long result = stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString()
);

3,調用阻塞隊列BlockingQueue<VoucherOrder>的數組實現并指定大小,將創建好的訂單加入到阻塞隊列,方法即可結束,大大提升性能。

private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024 * 1024);
VoucherOrder voucherOrder = new VoucherOrder();
//訂單id,用戶id,優惠卷id
long orderId = redisIdWorker.nextId("order");
voucherOrder.setId(orderId);
voucherOrder.setUserId(userId);
voucherOrder.setVoucherId(voucherId);
orderTasks.add(voucherOrder);

4,開啟獨立線程處理將訂單寫入數據庫,加上@PostConstruct注解保證在類初始化之后立即執行

private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();@PostConstructprivate void init() {SECKILL_ORDER_EXECUTOR.submit(() -> {while (true) {try {//獲取隊列的頭部,如果需要則等待直到元素可用為止VoucherOrder order = orderTasks.take();handleVoucherOrder(order);} catch (Exception e) {log.error("處理訂單異常", e);}}});}

5,order傳入上鎖的方法handleVoucherOrder(),防止同一個用戶的多個請求并發產生的問題,保證每個請求(線程)單獨執行

    private void handleVoucherOrder(VoucherOrder order) {RLock lock = redissonClient.getLock(LOCK_ORDER_KEY + order.getUserId());try {boolean isLock = lock.tryLock();if (!isLock) {log.error("操作頻繁,請稍后重試!");return;}proxy.createVoucherOrder(order);} finally {lock.unlock();}}

6,編寫訂單寫入數據庫的方法createVoucherOrder()并開啟事務,這樣保證鎖的范圍比事務范圍大,避免出現事務未提交鎖提前釋放的問題。

    @Transactionalpublic void createVoucherOrder(VoucherOrder order) {//一人一單判斷Long userId = order.getUserId();Long voucherId = order.getVoucherId();long count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();if (count > 0) {log.error("不能重復下單!");return;}//扣減庫存//這個sql語句是原子性的操作,而LambdaUpdateWrapper表達式不是boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock", 0).update();if (!success) {log.error("庫存不足!");return;}save(order);}

?

二,redis實現消息隊列

(1)基于list模擬消息隊列

(2)基于pubsub的消息隊列

(3)基于stream類型的消息隊列

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

1,創建STREAM數據stream.order作為阻塞隊列和消費組g1

xgroup create stream.order g1 0 mkstream

2,lua腳本增加發送消息到隊列中的操作和訂單id

local orderId = ARGV[3]
redis.call('xadd', 'stream.order', '*', 'userId', userId,'voucherId', voucherId, 'id', orderId)

3,java客戶端獲取消息隊列中的消息

(1)獲取消息隊列的訂單信息,redis命令:XREADGROUP GROUP g1 c1 count 1 block 2000 streams stream.order >(消費者c1,讀取數量1,阻塞時間2000毫秒,>表示從當前消費者組中未消費的最新的消息開始讀取)

(2)如果返回的結果為空或者list是空集合,說明沒有獲取到,continue后面操作重新獲取;獲取成功,取出消息MapRecord,取出訂單信息鍵值對record.getValue(),最后填入VoucherOrder即可

(3)ack確認消息xack stream.order g1 id

while (true) {try {//獲取消息隊列的訂單信息XREADGROUP GROUP g1 c1 count 1 block 2000 streams stream.order >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()) {continue;}//獲取成功,處理消息隊列中的訂單信息MapRecord<String, Object, Object> record = list.getFirst();Map<Object, Object> value = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);handleVoucherOrder(voucherOrder);//ack確認消息xack stream.order g1 idstringRedisTemplate.opsForStream().acknowledge(queueName, "g1", record.getId());} catch (Exception e) {log.error("處理訂單異常", e);//如果中間出現異常那么就在pending-list中保存訂單信息handlePendingList();}
}

4,如果中間出現異常那么就在pending-list中保存訂單信息

(1)獲取pending-list中的訂單信息,redis命令:XREADGROUP GROUP g1 c1 count 1 streams stream.order 0(0表示從開始位置讀取消息)

(2)獲取失敗,說明pending-list里面沒有異常消息,那么直接結束循環;如果再次出現異常,記錄日志,休眠線程一段時間 ,重新開始循環。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

    private void handlePendingList() {while (true) {try {List<MapRecord<String, Object, Object>> list = stringRedisTemplate.opsForStream().read(Consumer.from("g1", "c1"),StreamReadOptions.empty().count(1),StreamOffset.create(queueName, ReadOffset.from("0")));if (list == null || list.isEmpty()) {break;}MapRecord<String, Object, Object> record = list.getFirst();//獲取訂單鍵值對Map<Object, Object> value = record.getValue();VoucherOrder voucherOrder = BeanUtil.fillBeanWithMap(value, new VoucherOrder(), true);handleVoucherOrder(voucherOrder);stringRedisTemplate.opsForStream().acknowledge(queueName, "g1", record.getId());} catch (Exception e) {log.error("處理pending-list異常", e);try {Thread.sleep(20);} catch (InterruptedException ex) {throw new RuntimeException(ex);}}}}

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

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

相關文章

【Java Web】速通HTML

參考筆記: JavaWeb 速通HTML_java html頁面-CSDN博客 目錄 一、前言 1.網頁組成 1 結構 2 表現 3 行為 2.HTML入門 1 基本介紹 2 基本結構 3. HTML標簽 1 基本說明 2 注意事項 4. HTML概念名詞解釋 二、HTML常用標簽匯總 + 案例演示 1. 字體標簽 font (1)定義 (2)案例 2…

Oracle/openGauss中,DATE/TIMESTAMP與數字日期/字符日期比較

ORACLE 運行環境 openGauss 運行環境 0、前置知識 ORACLE&#xff1a;DUMP()函數用于返回指定表達式的數據類型、字節長度及內部存儲表示的詳細信息 SELECT DUMP(123) FROM DUAL; -- Typ2 Len3: 194,2,24 SELECT DUMP(123) FROM DUAL;-- Typ96 Len3: 49,50,51 -- ASCII值&am…

[學習]C++ 模板探討(代碼示例)

C 模板探討 文章目錄 C 模板探討一、模板基礎概念二、函數模板三、類模板1. 類模板的定義與使用2. 成員函數模板3. 類模板的靜態成員與繼承 四、模板進階特性1. 非類型模板參數2. 可變參數模板&#xff08;Variadic Templates&#xff09;3. 模板元編程&#xff08;TMP&#xf…

人工智能-訓練AI模型涉及多個步驟

訓練AI模型涉及多個步驟&#xff0c;包括數據預處理、選擇合適的模型、訓練模型以及評估模型性能。下面是一個詳細的流程&#xff0c;以常見的機器學習任務——分類問題為例&#xff0c;展示如何使用Python中的scikit-learn庫來訓練一個簡單的AI模型。 步驟 1: 導入所需的庫 …

LVS+Keepalived 高可用

目錄 一、核心概念 1. LVS&#xff08;Linux Virtual Server&#xff09; 2. Keepalived 二、高可用架構設計 1. 架構拓撲圖 2. 工作流程 三、部署步驟&#xff08;以 DR 模式為例&#xff09; 1. 環境準備 2. 主 LVS 節點配置 &#xff08;1&#xff09;安裝 Keepali…

TCP 三次握手過程詳解

TCP 三次握手過程詳解 一、TCP握手基礎概念 1.1 什么是TCP握手 TCP三次握手是傳輸控制協議(Transmission Control Protocol)在建立連接時的標準過程,目的是確保通信雙方具備可靠的雙向通信能力。 關鍵結論:三次握手的本質是通過序列號同步和能力協商建立可靠的邏輯連接。 …

李宏毅NLP-7-CTC/RNN-T文本對齊

LAS LAS&#xff08;Listen, Attend and Spell &#xff09;模型&#xff0c;在語音識別中的解碼和訓練過程&#xff0c;具體內容如下&#xff1a; 解碼&#xff08;Decoding&#xff09; 公式 Y ? arg ? max ? Y log ? P ( Y ∣ X ) Y^* \arg\max_Y \log P(Y|X) Y?ar…

jQuery和CSS3卡片列表布局特效

這是一款jQuery和CSS3卡片列表布局特效。該卡片布局使用owl.carousel.js來制作輪播效果&#xff0c;使用簡單的css代碼來制作卡片布局&#xff0c;整體效果時尚大方。 預覽 下載 使用方法 在頁面最后引入jquery和owl.carousel.js相關文件。 <link rel"stylesheet&qu…

Microsoft 推出 Magentic-UI,多智能體引領網頁人機協作變革

當前&#xff0c;現代生產力與網頁操作緊密相連&#xff0c;信息檢索、表單填寫、儀表盤導航等網頁任務已成為工作流程的重要環節。然而&#xff0c;大量網頁任務仍依賴人工重復操作&#xff0c;效率低下且易出錯。與此同時&#xff0c;許多 AI 智能體雖追求自主運行&#xff0…

2023年6級第一套長篇閱讀

畫名詞概念&#xff0c;動詞概念 多處定位原詞加同義改寫 畫關鍵詞&#xff0c;多處定位直接就可以選A了 沒有定位的句子先比沒匹配到的段落&#xff0c;再匹配長的段落先易后難

登山第二十三梯:有序點云平面快速分割——35Hz幀速前進

文章目錄 一 摘要 二 資源 三 內容 一 摘要 3D 點云中的實時平面提取對于許多機器人應用至關重要。作者提出了一種新穎的算法&#xff0c;用于在從 Kinect 傳感器等設備獲得的有組織的點云中實時可靠地檢測多個平面。通過在圖像空間中將這樣的點云均勻地劃分為不重疊的點組&…

【北京盈達科技】GEO優化:引領AI時代內容霸權,重塑行業生態

盈達科技GEO優化&#xff1a;引領AI時代內容霸權&#xff0c;重塑行業生態 在人工智能飛速發展的今天&#xff0c;生成式AI已經深刻改變了人們獲取信息的方式。從ChatGPT到文心一言&#xff0c;再到各種智能問答系統&#xff0c;AI生成的內容正在成為信息傳播的新主流。然而&a…

安卓端智能耗材柜系統可行性方案(基于uniapp + Vue3)

一、系統架構設計 1. 技術棧&#xff1a; 前端框架&#xff1a;uniapp Vue3 TypeScript狀態管理&#xff1a;Pinia&#xff08;分層設計&#xff0c;模塊化Store&#xff09;硬件交互&#xff1a;Android原生插件&#xff08;Java/Kotlin封裝&#xff09;通信協議&#xff…

Java交互協議詳解:深入探索通信機制

解析Java中各類交互協議的設計原理與實戰應用&#xff0c;涵蓋TCP/UDP自定義協議、HTTP/RESTful、WebSocket、RPC等主流方案。 一、交互協議核心概念 交互協議是系統間通信的規則集合&#xff0c;包含&#xff1a; 消息格式&#xff1a;數據序列化方式&#xff08;JSON/XML/P…

k8s上運行的mysql、mariadb數據庫的備份記錄

文章目錄 前言一、獲取需要備份的數據庫的信息二、備份步驟1.準備工作2.手動備份3.定時任務自動備份 總結 前言 記錄一下在k8s運行的數據庫的備份步驟。 我的思路是新建一個數據庫的容器作為工具容器&#xff0c;通過工具容器執行mysqldump命令進行備份&#xff0c;最后通過定…

寶塔面板部署python web項目詳細教程

最近在學langchain&#xff0c;寫了一個小案例出來&#xff0c;我剛好有一臺服務器&#xff0c;就嘗試自己部署一下項目&#xff0c;結果很幸運一遍過&#xff0c;現在記錄一下。我的系統是OpenCloudOS 9 目錄 1.安裝python解釋器版本 2.上傳項目文件到寶塔面板 3.添加項目…

IT選型指南:電信行業需要怎樣的服務器?

從第一條電報發出的 那一刻起 電信技術便踏上了飛速發展的征程 百余年間 將世界編織成一個緊密相連的整體 而在今年 我們迎來了第25屆世界電信日 同時也是國際電聯成立的第160周年 本屆世界電信日的主題為:“彌合性別數字鴻溝,為所有人創造機遇”,但在新興技術浪潮洶涌…

OAuth協議中的Token、Ticket

OAuth協議中的核心概念&#xff08;如Token、Ticket等&#xff09;可以通過日常生活中的類比來形象理解&#xff1a; 1. 門票&#xff08;Ticket&#xff09; vs 令牌&#xff08;Token&#xff09;類比 概念現實類比OAuth中的表現Ticket電影院紙質票&#x1f3ab;短期有效的臨…

80x86CPU入棧與出棧操作

一、棧操作&#xff1a;入棧push&#xff0c;出棧pop 棧操作&#xff1a;FILO&#xff08;先進后出機制&#xff09; 棧頂的指針&#xff1a;ss:sp決定&#xff0c;任意時刻棧頂指針指向SS:SP的位置 對于8086CPU 入棧時&#xff1a;sp-2 出棧時&#xff1a;sp2 assume cs…

最優控制:從變分法到龐特里亞金原理

典型問題 根據系統的建模可以劃分為&#xff1a; 線性系統&#xff1a; x ˙ A x B u \mathbf{\dot{x}} \boldsymbol{A}\mathbf{x}\boldsymbol{B}\mathbf{u} x˙AxBu非線性系統 x ˙ ( t ) f ( x ( t ) , u ( t ) , t ) \dot{\mathbf{x}}(t) \mathbf{f}(\mathbf{x}(t)…