使用Redis完成商品秒殺業務

在這里插入圖片描述

??各位小伙伴們大家好,歡迎來到這個小扎扎的Redis 6專欄,在這個系列專欄中我對B站黑馬的Redis教程進行一個總結,鑒于 看到就是學到、學到就是賺到 精神,這波依然是血賺 ┗|`O′|┛

💡Redis知識點速覽

  • 🍖 全局唯一ID
    • 🥩 業務邏輯分析
    • 🥩 代碼實現
  • 🍖 優惠券秒殺
    • 🥩 業務邏輯分析
    • 🥩 代碼實現
  • 🍖 定量商品多賣問題
    • 🥩 業務邏輯分析
    • 🥩 樂觀鎖與悲觀鎖
    • 🥩 樂觀鎖代碼實現
  • 🍖 一個用戶限買一單
    • 🥩 業務邏輯分析
    • 🥩 代碼實現

🍖 全局唯一ID

🥩 業務邏輯分析

??全局唯一ID是針對銷量比較大的一些商品而言的,這類商品的成交量比較多,用戶購買成功就會生成對應訂單信息并保存到一張表中,而訂單表的id如果使用數據庫自增ID就存在一些問題,比如說id的規律性太強導致安全性極低,還有如果訂單數量太多一張表存不下分成多張表存儲的話就會出現ID沖突問題,于是我們需要一個全局ID生成器,保證ID在全局中都是唯一的

??使用Redis即可完成這種全局ID生成器的功能,具體實現就是一種類雪花算法,也就是符號位、時間戳、序列號三部分拼接形成一個ID,邏輯就是符號位0代表整數,時間戳確定具體到下訂單的時候是哪一秒,至于序列號就是用于區分這一秒的訂單,序列號使用redis的值自增來保證所有序列號不一致,原則上一秒中最多可以有232個不同的ID在這里插入圖片描述

🥩 代碼實現

/*** @author : mereign* @date : 2022/5/11 - 14:06* @desc : 全局ID生成器*/@Component
public class RedisIdGenerator {/*** 構造方法注入stringRedisTemplate對象*/private StringRedisTemplate stringRedisTemplate;public RedisIdGenerator(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}// 定義序列號的位數private static final int COUNT_BITS = 30;public long nextId(String keyPrefix) {// 生成從指定時間到現在的時間戳LocalDateTime beginTime = LocalDateTime.of(2022, 1, 1, 0, 0, 0);long beginTimeStamp = beginTime.toEpochSecond(ZoneOffset.UTC);long endTimeStamp = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);long timeStamp = endTimeStamp - beginTimeStamp;/*** 生成序列號 使用redis的incr方法 K值為"icr:" + keyPrefix + ":" + date* 也就是按照日期作為K 每下一次單V就自增1作為序列號添加到后面* 這樣的話既避免了K固定帶來的V超過最大閾值(redis中的V最大為2^64)* 而且還方便了統計一天、一個月、一年的訂單量,在這段時間內最大的序列號就是它的最多訂單數*/String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));Long sequenceId = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);// 拼接生成全局唯一ID并返回 兩個二進制的拼接可以使用前一個數左移一定位數 后一個數與位移后的進行或運算return timeStamp << COUNT_BITS | sequenceId;}
}

🍖 優惠券秒殺

🥩 業務邏輯分析

??用戶對秒殺商品下單的時候,后臺業務需要先完成對商品時間的判斷,判斷該商品的秒殺活動是否開始或者有沒有結束,但凡還未開始或者已經結束都無法下單;時間信息正確的話就判斷該商品的活動庫存還有沒有剩余,如果已經賣完的話也無法下單。時間和庫存的判斷都是通過前端傳過來的優惠券id,查出來該優惠券的時間和庫存信息,如果條件都滿足的話,將該商品券的庫存扣除,然后創建訂單返回訂單id

🥩 代碼實現

??controller層主要就是調用service接口里的secKillVoucher方法,所以整個業務邏輯代碼全部都在接口的實現類中完成

@Resource
private ISeckillVoucherService seckillVoucherService;@Resource
private RedisIdGenerator generator;@Override
@Transactional
public Result secKillVoucher(Long voucherId) {// 查詢優惠券SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);// 獲取時間 判斷秒殺活動是否開始或者結束if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("活動暫未開始");} else if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("活動已經結束");}// 判斷庫存是否充足if (seckillVoucher.getStock() < 1) {return Result.fail("庫存不足,活動結束");}// 扣減庫存seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();// 創建訂單 并返回idVoucherOrder order = new VoucherOrder();// 訂單id(redis全局唯一id) 下單用戶id(攔截器中做登錄驗證的用戶id) 優惠券id(直接傳過來的id)long orderId = generator.nextId("order");order.setId(orderId);order.setUserId(UserHolder.getUser().getId());order.setVoucherId(voucherId);save(order);return Result.ok(orderId);
}

🍖 定量商品多賣問題

🥩 業務邏輯分析

??像上面的優惠券秒殺的業務,優惠券或者商品的數量一般都是固定的,如果把這些數量都賣完之后應該就結束這個活動。但是現實中的秒殺業務都是多線程的,很多的用戶同時等著活動開啟一起點擊下單,這樣的話就極有可能出現線程安全問題也就是說最終成交的數量要多于活動商品的數量

??上述問題出現的原因就是多線程之間的執行順序所引起,我們的秒殺業務里面是先查詢庫存數量大于1就產生訂單,但是多線程之間的執行不會嚴格的按照這個順序執行,而是交叉執行,如果最后只剩一張票的時候進來了兩個線程AB,A查完B查AB查詢結果都可以下單,A產生訂單B再產生訂單,此時就已經產生超賣

🥩 樂觀鎖與悲觀鎖

??解決線程問題的最好方法就是加鎖,但是鎖也分為悲觀鎖和樂觀鎖,悲觀鎖認為線程安全問題一定會發生,因此在操作數據之前先獲取鎖,確保線程串行執行,例如Synchronized、Lock等。樂觀鎖認為線程安全問題不一定會發生,因此不加鎖,只是在更新數據時去判斷有沒有其它線程對數據做了修改,如果沒有修改則更新數據,修改說明發生了安全問題

??很顯然樂觀鎖的性能要顯著高于悲觀鎖,因此采用樂觀鎖保證線程的原子性。樂觀鎖又有兩種解決方案:版本號是指對修改的數據附帶一個version字段值,每次更新的時候判斷修改時的version與查詢的時候是否一致,一致則修改。CAS機制全稱為Compare And Swap譯為先比較再交換,也就是將修改的數據本身作為版本號,每次更新的時候判斷修改時的數據值與查詢時的值是否相同,相同則修改,不同就說明發生了線程安全問題,在我們的這個售賣業務中,可以設置成只要庫存大于0就可以執行成功

🥩 樂觀鎖代碼實現

??樂觀鎖的核心就是,在更新數據的時候(也就是減少庫存),判斷一下庫存是否大于0,如果判斷失敗的話也應該使該線程任務失敗

// 扣減庫存
boolean update = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock", 0).update();
// 更新失敗說明在扣除庫存的時候 庫存小于等于0
if (!update) {return Result.fail("庫存不足!");
}

🍖 一個用戶限買一單

🥩 業務邏輯分析

??按照正常的業務邏輯,秒殺應該限制一個用戶只能購買一次該商品,最簡單的方法就是對user_id使用唯一索引,如果user_id重復就會拋出相關異常,但是這需要修改表結構。如果不修改標結果的話就需要扣除庫存之前根據voucher_id和user_id查詢訂單表,如果存在的話就返回錯誤,否則說明該用戶還未購買

🥩 代碼實現

??單機(服務部署在一臺tomcat服務器)的情況下,加synchronized 鎖即可解決(查詢判斷用戶是否下單和創建訂單)業務的線程安全問題,但是這種情況就只能

// 單用戶id(攔截器中做登錄驗證的用戶id)
Long userId = UserHolder.getUser().getId();// 根據user_id加鎖  intern方法是去字符常量池中查找值相同的,不加的話字符串值一樣的地址不一樣也會加上鎖
synchronized (userId.toString().intern()) {// 查詢優惠券// 判斷庫存是否充足// user_id和voucher_id聯合查詢訂單數Integer count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();// 訂單數為1 就說明已經下過單了if (count.equals(1)) {return Result.fail("您已經購買過該商品了");}// 扣減庫存  創建訂單return Result.ok(orderId);
}

??以上加synchronized 鎖的解決方案只適用于單機模式下,此時所有的請求過來都會按照userId去常量池中查找是否一致,一致的話就鎖在一起防止一個用戶購買多單。但是集群模式下所有的請求會經過Nginx的負載均衡輪詢發送到集群上的所有服務器,如果一個用戶的多個請求被分配到不同的服務器上的話,不同服務器中的JVM虛擬機里的靜態常量池中的內容是不同步的,這樣的話就會導致雖然userId一致但是各自所在的靜態常量池中都沒有,于是這個用戶就可以在不同的服務器分別下單了。如果有用戶使用腳本同時發送很多的下單請求,那么就會有極大的可能在每一個服務器中都下一單,那么如何解決這個問題呢?那就要學習分布式鎖的內容了

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

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

相關文章

表格列隱藏_【excel每日提升】Excel隱藏列,不讓別人打開!

【新朋友】點擊標題下面藍色字“王俊東“關注。 【老朋友】點擊右上角&#xff0c;轉發或分享本頁面內容。excel系列課程excel特效系列課程開始了&#xff0c;今天第2節&#xff01;第1節&#xff1a;Excel有公式的單元格標記顏色&#xff0c;很簡單&#xff01;第2節&#xf…

map與weakmap,ES6 Map和WeakMap有什么區別?

Looking this and this MDN pages it seems like the only difference between Maps and WeakMaps is a missing "size" property for WeakMaps. But is this true? Whats the difference between them?解決方案The experienced JavaScript programmer will notice…

“畢業季”|一個java開發實習生的OFFER之路

哈嘍哈嘍大家好&#xff0c;這里是小扎扎的博客。相信有關注過我的好盆友們可能會發現我已經有一段時間沒有出來劃水了&#xff0c;那么這段時間小扎扎都在干什么呢&#xff1f;沒錯&#xff01;我確實是去找實習了&#xff01;接下來就給大家介紹一下本次戰役的戰況如何 活動地…

virtualbox 該內存不能為written_系統提示“該內存不能為read”的原因和解決辦法...

我們單位的電腦經常顯示這個對話框&#xff0c;已經有好幾年了&#xff0c;單位的老頭們都不怎么懂電腦&#xff0c;我本人也不愛管閑事。但是出現這種對話框的原因是什么呢&#xff1f;又怎么解決呢&#xff1f;一般電腦經常出現藍屏和死機&#xff0c;而且頻繁出現。有時會出…

插件properties_Mybatis3系列 - 4. mybatis-config的properties屬性詳解

前兩章簡單的講解了MyBatis的使用方式. 接下來先全局的說一下MyBatis的全局的xml配置詳細說明.XML格式定義-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">configuration 的映射文件(根據定義順序說明)properties 屬性…

【Redis 6】秒殺業務——分布式鎖

各位小伙伴們大家好&#xff0c;歡迎來到這個小扎扎的Redis 6專欄&#xff0c;在這個系列專欄中我對B站黑馬的Redis教程進行一個總結&#xff0c;鑒于 看到就是學到、學到就是賺到 精神&#xff0c;這波依然是血賺 ┗|&#xff40;O′|┛ &#x1f4a1;Redis知識點速覽&#…

python讀取文件最后幾行_如何用python獲取文件的最后一行,文件可能會比較大

展開全部 #!/usr/bin/env python import os import sys def get_last_n_lines(logfile, n): blk_size_max 4096 n_lines [] with open(logfile, rb) as fp: fp.seek(0, os.SEEK_END) cur_pos fp.tell() while cur_pos > 0 and len(n_lines) < n: blk_size min(blk_si…

360oauth token是什么意思_Oauth/access token

oauth/access_token第三放應用使用開始的request_token來換取用戶授權過的Access_tokenURL格式標準的OAuth http返回格式HTTP請求方式POST請求數限制false請求參數oauth_consumer_key: 創建應用時生成的APP KEY。oauth_token:經過用戶授權的Request Token。oauth_signature_met…

【Swagger】看這一篇就夠了

各位小伙伴們大家好&#xff0c;歡迎跟著小扎扎一起學習【Swagger】這門技術&#xff0c;在本片博客中我對B站狂神的Swagger教程進行一個總結&#xff0c;鑒于 看到就是學到、學到就是賺到 精神&#xff0c;這波依然是血賺 ┗|&#xff40;O′|┛ &#x1f4a1;Swagger知識點…

python mockito arg_that_wqingxiao

偶然間在脈脈上看到了一道頭條的算法面試題按照題目的理解&#xff0c;簡單的寫了一個html網頁pool開始{{index}}{{index}}var vm new Vue({el: #vue_det,data: {list: [],i: 0},methods: {details: function () {return this.site " - 學的不僅是技術&#xff0c;更是夢…

用python畫大雄_python制作斗圖生成器

網上各種帶文字的表情圖片都被大家玩壞了&#xff0c;今天就和大家一起用 python 親自做一個帶字表情圖片生成器。 不知道大家有沒有看到網上有很多人都在說 "人生苦短&#xff0c;我用 python"&#xff0c;這句話我之前也不是很理解&#xff0c;覺得人生苦短和用pyt…

【spring cloud】(一)使用idea創建可相互調用的多模塊應用

各位小伙伴們大家好&#xff0c;歡迎來到這個小扎扎的spring cloud專欄&#xff0c;在這個系列專欄中我對B站尚硅谷陽哥的spring cloud教程進行一個總結&#xff0c;鑒于 看到就是學到、學到就是賺到 精神&#xff0c;這波依然是血賺 ┗|&#xff40;O′|┛ &#x1f4a1;spr…

epson彩色打印機加墨水_愛普生打印機墨盒如何加墨?

展開全部掌握以下幾點步驟&#xff0c;即可輕松加墨。1、首先&#xff0c;從打印機上取下墨盒&#xff0c;32313133353236313431303231363533e4b893e5b19e31333365646234這里就不好做介紹了&#xff0c;取下墨盒的方法因機而異。將墨盒上的一層貼紙撕掉&#xff0c;并拿出所需材…

python字符串百分號_Python字符串格式化的2種方法

本文介紹了Python字符串格式化&#xff0c;主要有兩種方法&#xff0c;分享給大家&#xff0c;具體如下 用于字符串的拼接&#xff0c;性能更優。 字符串格式化有兩種方式&#xff1a;百分號方式、format方式。 百分號方式比較老&#xff0c;而format方式是比較先進的&#xff…

【踩坑,已解決】spring cloud刪除子模塊后創建同名模塊時遇到的刪除不凈,java、resources包失效,java、resources包被標記為模塊等問題

&#x1f4a1;問題速覽&#x1f4cc; 問題復現&#x1f4cc; 沒有任何操作&#xff0c;model又出現了&#x1f4cc; 模塊重建后java、resources包失效&#x1f4cc; 模塊重建后java、resources包被標記為了模塊&#x1f4cc; 問題復現 本人是在跟著B站尚硅谷的視頻練習Spring c…

藍牙掃描過程解析_智慧定位系統之藍牙網關在室內定位技術的原理淺析-新導智能...

藍牙室內定位技術是利用在室內安裝的若干個藍牙局域網接入點&#xff0c;把網絡維持成根據多用戶的基礎網絡連接形式&#xff0c;并確保藍牙局域網接入點始終是這個微網的主設備&#xff0c;然后經過丈量信號強度對新加入的盲節點進行三角定位。蘇州新導實時定位體系(RTLS)和室…

【spring cloud】(二)服務的注冊發現——Eureka

各位小伙伴們大家好&#xff0c;歡迎來到這個小扎扎的spring cloud專欄&#xff0c;在這個系列專欄中我對B站尚硅谷陽哥的spring cloud教程進行一個總結&#xff0c;鑒于 看到就是學到、學到就是賺到 精神&#xff0c;這波依然是血賺 ┗|&#xff40;O′|┛ &#x1f4a1;spr…

python選擇題題庫百度文庫_大學Python程序題題庫

程序設計 題目&#xff1a;補充 fun 函數&#xff0c;其功能是判斷一個整數是否是素數&#xff0c;在主 函數 main 中輸入一個整數&#xff0c;調用該 fun 函數進行判斷并輸 出結果。 要求&#xff1a;使用 math 相關函數 import math def fun(n): i,w2,0 if n<1: w1 while …

新手攻略熔爐_我的世界攻略:生存模式新手攻略

《我的世界》(英文:《minecraft》)是一款風靡全球的高自由度沙盒游戲&#xff0c;由瑞典MojangAB和4J Studios開發。國際版由微軟Mojang工作室開發&#xff0c;中國版由網易代理。Minecraft著重于讓玩家去探索、交互&#xff0c;并且改變一個由一立方米大小的方塊動態生成的地圖…

【spring cloud】(三)服務調用——Ribbon、OpenFeign

各位小伙伴們大家好&#xff0c;歡迎來到這個小扎扎的spring cloud專欄&#xff0c;在這個系列專欄中我對B站尚硅谷陽哥的spring cloud教程進行一個總結&#xff0c;鑒于 看到就是學到、學到就是賺到 精神&#xff0c;這波依然是血賺 ┗|&#xff40;O′|┛ &#x1f4a1;服務…