【Redis實戰篇】秒殺優化

1. 秒殺優化-異步秒殺思路

我們來回顧一下下單流程

當用戶發起請求,此時會請求nginxnginx會訪問到tomcat,而tomcat中的程序,會進行串行操作,分成如下幾個步驟

  • 1、查詢優惠卷

  • 2、判斷秒殺庫存是否足夠

  • 3、查詢訂單

  • 4、校驗是否是一人一單

  • 5、扣減庫存

  • 6、創建訂單

在這里插入圖片描述
在這六步操作中,由于有很多操作是要去操作數據庫的,而且還是一個線程串行執行,添加了分布式鎖, 這樣就會導致我們這段程序執行耗時比較長,并發能力變弱了,所以我們需要異步程序執行,那么如何優化呢?

在這里筆者想給大家分享一下課程內沒有的思路,看看有沒有小伙伴這么想,比如,我們可以不可以使用異步編排來做,或者說我開啟N多個線程,一個線程執行查詢優惠卷,一個執行判斷扣減庫存,一個去創建訂單等等,然后再統一做返回,這種做法和課程中有哪種好呢?答案是課程中的好,因為如果你采用我剛說的方式,如果訪問的人很多,那么線程池中的線程可能一下子就被消耗完了,而且你使用上述方案,最大的特點在于,你覺得時效性會非常重要,但是你想想是嗎?并不是,比如我只要確定他能做這件事,然后我后邊慢慢做就可以了,我并不需要他一口氣做完這件事,所以我們應當采用的是課程中,類似消息隊列的方式來完成我們的需求,而不是使用線程池或者是異步編排的方式來完成這個需求。

在這里插入圖片描述

優化方案:我們將耗時比較短的邏輯判斷放入到redis中,比如是否庫存足夠,比如是否一人一單,這樣的操作,只要這種邏輯可以完成,就意味著我們是一定可以下單完成的,我們只需要進行快速的邏輯判斷,根本就不用等下單邏輯走完,我們直接給用戶返回成功, 再在后臺開一個線程,后臺線程慢慢的去執行阻塞queue里邊的消息,這樣程序不就超級快了嗎?而且也不用擔心線程池消耗殆盡的問題,因為這里我們的程序中并沒有手動使用任何線程池,當然這里邊有兩個難點:

  • 第一個難點是我們怎么在redis中去快速校驗一人一單,還有庫存判斷

  • 第二個難點是由于我們校驗和tomcat下單是兩個線程,那么我們如何知道到底哪個單他最后是否成功,或者是下單完成,為了完成這件事我們在redis操作完之后,我們會將一些信息返回給前端,同時也會把這些信息丟到異步queue中去,后續操作中,可以通過這個id來查詢我們tomcat中的下單邏輯是否完成了。

在這里插入圖片描述

我們現在來看看整體思路:

  1. 選擇Redis存儲結構: 用戶下單我們只需要存儲庫存變量即可,可以直接使用string類型結構,而一人一單問題,我們是需要存儲很多用戶的購買記錄,并且用戶不能重復下單,所以這里我們選擇set存儲結構再適合不過。
  2. 邏輯:當用戶下單之后,判斷庫存是否充足只需要到redis中去根據key找對應的value是否大于0即可,如果不充足,則直接結束,如果充足,繼續在redis中判斷用戶是否可以下單,如果set集合中沒有這條數據,說明他可以下單,并扣減庫存,再將userId存入當前優惠券的set中,并且返回0,整個過程需要保證是原子性的,我們可以使用lua來操作。當以上判斷邏輯走完之后,我們可以判斷當前redis中返回的結果是否是0 ,如果是0,則表示可以下單,則將之前說的信息存入到阻塞queue中去,此時開啟單獨的線程異步寫入數據庫中,最后返回訂單id,前端可以通過返回的訂單id來判斷是否下單成功。

2. 秒殺優化-Redis完成秒殺資格判斷

需求:

  • 新增秒殺優惠券的同時,將優惠券信息保存到Redis中

  • 基于Lua腳本,判斷秒殺庫存、一人一單,決定用戶是否搶購成功

  • 如果搶購成功,將優惠券id和用戶id封裝后存入阻塞隊列

  • 開啟線程任務,不斷從阻塞隊列中獲取信息,實現異步下單功能

在這里插入圖片描述
VoucherServiceImpl👇 將用戶資格判斷放入redis中,用戶響應信息只有和redis操作,性能大幅提高,新增秒殺優惠券的同時,將優惠券庫存同步到redis中

@Override
@Transactional
public void addSeckillVoucher(Voucher voucher) {// 保存優惠券save(voucher);// 保存秒殺信息SeckillVoucher seckillVoucher = new SeckillVoucher();seckillVoucher.setVoucherId(voucher.getId());seckillVoucher.setStock(voucher.getStock());seckillVoucher.setBeginTime(voucher.getBeginTime());seckillVoucher.setEndTime(voucher.getEndTime());seckillVoucherService.save(seckillVoucher);// 保存秒殺庫存到Redis中//SECKILL_STOCK_KEY 這個變量定義在RedisConstans中//private static final String SECKILL_STOCK_KEY ="seckill:stock:"stringRedisTemplate.opsForValue().set(SECKILL_STOCK_KEY + voucher.getId(), voucher.getStock().toString());
}

seckill.lua腳本👇 對redis的查詢、判斷、數據操作寫入一個lua腳本,防止線程并發安全問題

-- 1.參數列表
-- 1.1.優惠券id
local voucherId = ARGV[1]
-- 1.2.用戶id
local userId = ARGV[2]
-- 1.3.訂單id
local orderId = ARGV[3]-- 2.數據key
-- 2.1.庫存key
local stockKey = 'seckill:stock:' .. voucherId
-- 2.2.訂單key
local orderKey = 'seckill:order:' .. voucherId-- 3.腳本業務
-- 3.1.判斷庫存是否充足 get stockKey
if(tonumber(redis.call('get', stockKey)) <= 0) then-- 3.2.庫存不足,返回1return 1
end
-- 3.2.判斷用戶是否下單 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then-- 3.3.存在,說明是重復下單,返回2return 2
end
-- 3.4.扣庫存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
-- 3.5.下單(保存用戶)sadd orderKey userId
redis.call('sadd', orderKey, userId)
return 0

寫完lua腳本,剩下的就是執行lua腳本,根據腳本拿到的結果,為非0,根據情況返回"庫存不足" 或者 "不能重復下單;為0,返回全局id生成器生成的訂單id。至于訂單保存到阻塞隊列中的邏輯,咱們稍后實現。

VoucherOrderServiceImpl
在這里插入圖片描述

3. 秒殺優化-基于阻塞隊列實現秒殺優化

創建阻塞隊列,保存數據類型為VoucherOrder,并設置大小為 1024 * 1024
在這里插入圖片描述

在這里插入圖片描述
處理訂單操作的邏輯:👇
在這里插入圖片描述

4. 總結

秒殺業務的優化思路是什么?

  • 先利用Redis完成庫存余量、一人一單判斷,完成搶單業務
  • 再將下單業務放入阻塞隊列,利用獨立線程異步下單

基于阻塞隊列的異步秒殺存在哪些問題?

  • 內存限制問題(使用jdk中的阻塞隊列,以后如果有大量的訂單需要創建,很容易出現OOM問題)
  • 數據安全問題(數據是存儲在內存里的,若出現服務宕機了,任務還沒執行完畢,導致用戶的訂單數據丟失)

針對此問題,我們將會再下一篇解決。

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

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

相關文章

【idea】調試篇 idea調試技巧合集

前言&#xff1a;之前博主寫過一篇idea技巧合集的文章&#xff0c;由于技巧過于多了&#xff0c;文章很龐大&#xff0c;所以特地將調試相關的技巧單獨成章, 調試和我們日常開發是息息相關的&#xff0c;用好調試可以事半功倍 文章目錄 1. idea調試異步線程2. idea調試stream流…

postman 用法 LTS

postman 用法 LTS File ---- View ---- Show Postman Console

MySQL 數據庫故障排查指南

MySQL 數據庫故障排查指南 本指南旨在幫助您識別和解決常見的 MySQL 數據庫故障。我們將從問題識別開始&#xff0c;逐步深入到具體的故障類型和排查步驟。 1. 問題識別與信息收集 在開始排查之前&#xff0c;首先需要清晰地了解問題的現象和范圍。 故障現象&#xff1a; 數…

用AI寫簡歷是否可行?

讓AI批量寫簡歷然后投簡歷是絕對不行的&#xff01;&#xff01;&#xff01; 為什么不行&#xff0c;按照 "招聘經理" 工作經歷舉例&#xff1a; ai提示詞&#xff1a;請幫我寫一份招聘經理的工作經歷內容&#xff1a; 招聘經理 | XXX科技有限公司 | 2020年…

【從零實現JsonRpc框架#1】Json庫介紹

1.JsonCpp第三方庫 JSONCPP 是一個開源的 C 庫&#xff0c;用于解析和生成 JSON&#xff08;JavaScript Object Notation&#xff09;數據。它提供了簡單易用的接口&#xff0c;支持 JSON 的序列化和反序列化操作&#xff0c;適用于處理配置文件、網絡通信數據等場景。 2.Jso…

Ubuntu——執行echo $USE什么都不顯示

問題&#xff1a;“執行 echo $USER 什么都不顯示”&#xff1f; 一、原因分析 環境變量 $USER 未正確設置 $USER 是系統自動定義的環境變量&#xff0c;通常用于表示當前登錄的用戶名。若該變量未設置或為空&#xff0c;執行 echo $USER 會無輸出。可能場景&#xff1a; 用戶通…

uni-app學習筆記五--vue3插值表達式的使用

vue3快速上手導航&#xff1a;簡介 | Vue.js 模板語法 插值表達式 最基本的數據綁定形式是文本插值&#xff0c;它使用的是“Mustache”語法 (即雙大括號)&#xff1a; <span>Message: {{ msg }}</span> 雙大括號標簽會被替換為相應組件實例中 msg 屬性的值。同…

【PSINS工具箱】基于工具箱的單獨GNSS導航、單獨INS導航、兩者結合組合導航,三種導航的對比程序。附完整的代碼

本文給出基于PSINS工具箱的單獨GNSS導航、單獨INS導航、兩者結合組合導航(153EKF)的程序。并提供三者的軌跡對比、誤差對比。 文章目錄 運行結果MATLAB代碼代碼的簡單介紹簡介2. 平均絕對誤差 (MAE)主要模塊運行結果 三軸軌跡圖: 各軸誤差曲線: 命令行窗口的結果輸出: …

C. scanf 函數基礎

scanf 函數 1. scanf 函數基礎1.1 函數原型與頭文件1.2 格式化輸入的基本概念2.1 常見格式說明符整數格式說明符浮點數格式說明符字符和字符串格式說明符其他格式說明符2.2 格式說明符的高級用法寬度修飾符精度修飾符跳過輸入字段寬度組合修飾符對齊修飾符實際應用示例3.2 精度…

spring cloud loadbalancer實現機房感知的負載均衡

1 概述 在同城多機房情景下&#xff0c;各個機房各自部署一套微服務集群&#xff0c;正常情況下微服務調用在本機房閉環。在如下某些災難情景&#xff0c;可以嘗試拉遠調用以最大程度維持業務連續性&#xff0c;這些情景例如&#xff1a; A機房多個服務器宕機。應用由于BUG發…

vue中,created和mounted兩個鉤子之間調用時差值受什么影響

在 Vue 中&#xff0c;created 和 mounted 是兩個生命周期鉤子&#xff0c;它們之間的調用時差主要受以下幾個因素影響&#xff1a; &#x1f7e2; 1. 模板復雜度與渲染耗時&#xff08;最主要因素&#xff09; mounted 的觸發時間是在組件的 DOM 被掛載之后&#xff08;也就是…

Linux篇 第2章Linux基礎指令

Linux篇 第2章Linux基礎指令 文章目錄 前言一、基礎的一些命令1.pwd2.mkdir3.ls4.cd5.clear 二、ls1.ls -l2.ls -a3.ls -l -a 三、touch四、 cd1.cd /2.cd ..3.cd ~4. cd - 五、tree1. Linux系統文件的結構2.絕對路徑和相對路徑 六、mkdir -p七、rmdir&#xff08;沒啥用&#…

Scrapyd 詳解:分布式爬蟲部署與管理利器

Scrapyd 是 Scrapy 官方提供的爬蟲部署與管理平臺&#xff0c;支持分布式爬蟲部署、定時任務調度、遠程管理爬蟲等功能。本文將深入講解 Scrapyd 的核心功能、安裝配置、爬蟲部署流程、API 接口使用&#xff0c;以及如何結合 Scrapy-Redis 實現分布式爬蟲管理。通過本文&#x…

國產免費工作流引擎star 6.5k,Warm-Flow升級1.7.2(新增案例和修復缺陷)

文章目錄 主要更新內容項目介紹功能思維導圖設計器流程圖演示地址官網Warm-Flow視頻 主要更新內容 [feat] 開啟流程實例&#xff0c;新增流程定義是否存在校驗[feat] 新增合同簽訂流程案例[feat] 新增企業采購流程案例[update] mybatis-plus邏輯刪除&#xff0c;刪除值和未刪除…

數據倉庫Hive

1.數據倉庫 1.1數據倉庫的概念 數據倉庫&#xff08;Data Warehouse&#xff09;是一個面向主題的、集成的、相對穩定的、反映歷史變化的數據集合&#xff0c;用于支持管理決策。 面向主題。操作型數據庫的數據組織面向事務處理任務&#xff0c;而數據倉庫中的數據按照一定的…

dify 連接不上ollama An error occurred during credentials validation:

三大報錯 An error occurred during credentials validation: HTTPConnectionPool(hosthost.docker.internal, port11434): Max retries exceeded with url: /api/chat (Caused by NameResolutionError("<urllib3.connection.HTTPConnection object at 0x7f26fc3c00b0&…

uniapp 生成海報二維碼 (微信小程序)

先下載qrcodenpm install qrcode 調用 community_poster.vue <template><view class"poster-page"><uv-navbar title"物業推廣碼" placeholder autoBack></uv-navbar><view class"community-info"><text clas…

如何理解編程中的遞歸、迭代與回歸?

作為編程初學者&#xff0c;遞歸、迭代和回歸這三個概念常常讓人感到困惑。本文將通過生活化的比喻、Python代碼示例和直觀的對比&#xff0c;幫助你徹底理解這三個重要概念及其應用場景。 一、從生活比喻理解核心概念 1. 遞歸&#xff08;Recursion&#xff09;—— 俄羅斯套…

Android Studio 模擬器配置方案

Android Studio 模擬器配置方案 1.引言2.使用Android Studio中的模擬器3.使用國產模擬器1.引言 前面介紹【React Native基礎環境配置】的時候需要配置模擬器,當時直接使用了USB調試方案,但是有些時候可能不太方便連接手機調試,比如沒有iPhone調不了ios。接下來說明另外兩種可…

uniapp(vue3)動態計算swiper高度封裝自定義hook

// useCalculateSwiperHeight.ts import { ref, onMounted } from vue;export function useCalculateSwiperHeight(headerSelector: string .header-search, tabsWrapperSelector: string .u-tabs .u-tabs__wrapper) {const swiperHeight ref<number>(0);// 封裝uni.g…