日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術(shù)文章
文章詳情頁

基于MySQL和Redis扣減庫存的實踐

瀏覽:326日期:2023-05-08 10:17:44
目錄
  • 背景
  • 環(huán)境搭建
    • 后臺系統(tǒng)
    • 中間件
    • 測試工具
  • 扣減模式
    • 基于數(shù)據(jù)庫行鎖 + CAS 實現(xiàn)庫存的扣減
    • 基于 Redis 實現(xiàn)庫存的扣減
  • 總結(jié)

    背景

    在很多情況下,扣減庫存是一個十分常見的需求,例如:學生選課系統(tǒng)中課程數(shù)量的扣減,抽獎系統(tǒng)中活動次數(shù)的扣減,電商系統(tǒng)中商品庫存的扣減等,都涉及到數(shù)量的扣減,這些系統(tǒng)在成功扣減的前提下,絕對不能出現(xiàn)庫存扣減多了的情況,也就是不能出現(xiàn)超賣。同時,我們也要注重系統(tǒng)性能的提升,這篇文章從這兩個角度進行分析和討論。

    環(huán)境搭建

    后臺系統(tǒng)

    基于 SpringBoot 搭建后臺系統(tǒng),JDK 為 1.8

    <properties>    <java.version>1.8</java.version>    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>    <spring-boot.version>2.3.12.RELEASE</spring-boot.version></properties><dependencies>    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>    </dependency>    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>    </dependency>    <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional>    </dependency>    <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version>    </dependency></dependencies>

    中間件

    中間件使用 MySQL + Redis 進行數(shù)據(jù)的存儲,使用 Mybatis 作為 ORM 框架

    create database t_desc collate utf8mb4_general_ci;use t_desc;create table t_good (    id bigint auto_increment primary key comment "自增id",    good_name varchar(255) not null comment "商品名稱",    stock int not null comment "商品庫存") comment "庫存測試表";insert into t_good(good_name, stock)  value("iphone", 50);

    創(chuàng)建一張商品庫存表,里面含有商品 id、商品名稱 和庫存 3 個字段,所有扣減庫存的操作都在這張表上進行;

    測試工具

    使用 JMeter 5.5 進行測試

    以下的庫存數(shù)量統(tǒng)一設(shè)置為 50 個,線程組的數(shù)量為 10 個,循環(huán) 10 次,共 100 個扣減請求,最終正確的結(jié)果應該是扣減完畢后庫存的數(shù)量應該為 0, 而不是 -50

    扣減模式

    基于數(shù)據(jù)庫行鎖 + CAS 實現(xiàn)庫存的扣減

    行鎖

    若直接直接在數(shù)據(jù)庫層面進行庫存的直接扣減,100 個線程同時進行請求,肯定會造成庫存的超賣

    SQL 語句為

    <update id="descGoodStock">  update t_desc.t_good  set t_good.stock = t_good.stock - 1  where id = #{id}</update>

    考慮到 update 語句,若根據(jù)主鍵索引作為條件進行更新,會對數(shù)據(jù)庫的某一行加上行鎖(數(shù)據(jù)庫開啟事務自動提交),所以我們加上 stock > 0 的判斷條件

    <update id="descGoodStockByLock">update t_desc.t_goodset t_good.stock = t_good.stock - 1where id = #{id}  and t_good.stock > 0</update>

    開啟 JMeter 進行測試,可見沒有超賣

    CAS

    CAS 即 Compare and Set,先把舊的庫存查出來,再把舊的庫存作為 update 的條件之一,若數(shù)據(jù)庫中的庫存與舊的庫存一致,則進行更新,否則不進行更新。

    其實本質(zhì)上與行鎖的方式?jīng)]什么區(qū)別,而且多了一次查詢,寫這個方法只是為了記錄而已

    若有兩個以上的線程先查詢到了商品的舊庫存,這種方法可能會出現(xiàn)扣不完的情況

    Java 代碼:

    @PostMapping("/db")public Map<String, Object> goodDescControllerByDataBase(Long id) {    HashMap<String, Object> ret = new HashMap<>();    // 查出舊的值    Good good = goodMapper.selectStockById(id);    // 再進行更新    int i = goodMapper.descGoodStockCAS(id, good.getStock());    if (i > 1) {ret.put("info", "success, 扣減成功");    } else {ret.put("info", "fail, 扣減失敗");    }    return ret;}

    SQL 語句

    <update id="descGoodStockCAS">update t_desc.t_goodset t_good.stock = t_good.stock - 1where id = #{id}  and t_good.stock = #{stock}  and t_good.stock > 0    </update>

    測試結(jié)果:

    綜上,基于數(shù)據(jù)庫的兩種扣減庫存的方式都沒有實現(xiàn)超賣,但是畢竟是數(shù)據(jù)庫,數(shù)據(jù)存儲于物理磁盤中,性能方面就有待考量;

    基于 Redis 實現(xiàn)庫存的扣減

    基本思想是:我們把庫存的數(shù)量提前放到 Redis 上,直接在 Redis 進行庫存的扣減

    • 先查詢 redis 中的庫存
    • 若小于 0 直接返回
    • 若大于 0 則進行 Redis 和 數(shù)據(jù)庫 中的庫存扣減

    不過這里存在 并發(fā) 問題,考慮極限情況,兩個線程同時獲得 stock = 1,然后再去進行庫存扣減,勢必會造成超賣的現(xiàn)象

    下面給出兩種解決辦法

    使用 decrement 方法

    redisTemplate.opsForValue().decrement():對某個 key 進行減 1 操作,會返回扣減后的值

    若該值大于等于 0 才進行數(shù)據(jù)庫的庫存的扣減,否則直接返回庫存不足的提示

    這種方法是基于 Redis 的指令是原子性的

    Java 代碼:

     @PostMapping("/redis")    public Map<String, Object> goodDescControllerByRedis(Long id) throws InterruptedException {HashMap<String, Object> ret = new HashMap<>();ret.put("info", "fail, 扣減失敗");// 查詢 Redis 中的庫存Integer stock = (Integer) redisTemplate.opsForValue().get(key + id);Thread.sleep(100);if (stock <= 0) {    return ret;}// 扣減 redis 中庫存Long decrement = redisTemplate.opsForValue().decrement(key + id);if (decrement >= 0) {    // 扣減數(shù)據(jù)庫庫存    goodMapper.descGoodStock(id);    ret.put("info", "success, 扣減成功");}return ret;    }

    其實 decrement 方法是原子性的,可以不用對庫存先進行查詢的操作,只需要判斷扣減后的數(shù)是否大于 0 即可。但是如果并發(fā)量高的話,建議還是加上判斷的邏輯,可以提高 Redis 的性能,不用每次進行 decrement 操作;

    缺點:這種辦法會導致 Redis 中庫存產(chǎn)生超賣現(xiàn)象,若對 Redis 中庫存數(shù)量要求準確,就不要使用這種方法;

    測試結(jié)果:

    Redis 中的庫存產(chǎn)生超賣現(xiàn)象:

    MySQL 中的庫存沒有超賣:

    使用 LUA 腳本

    上述問題的關(guān)鍵是:查詢 和 扣減 是兩個分開操作,不是一條原子性的命令。我們可以使用 LUA 腳本,把這兩條命令封裝到 LUA 代碼中,實現(xiàn)這兩個操作的原子性

    LUA 代碼

    ------ Generated by EmmyLua(https://github.com/EmmyLua)--- Created by Ezreal.--- DateTime: 2023/5/6 21:56---if (redis.call("exists", KEYS[1]) == 1) then    local stock = tonumber(redis.call("get", KEYS[1]));    if (stock <= 0) thenreturn -1;    end    if (stock > 0) thenredis.call("incrby", KEYS[1], -1);return 1;    endendreturn -1

    先獲取值,然后判斷庫存數(shù)量,若沒有小于等于 0 就先進行扣減即可

    Java 代碼

    private static final DefaultRedisScript<Long> DECREASE_GOOD_STOCK_SCRIPT = new DefaultRedisScript<>();static {    DECREASE_GOOD_STOCK_SCRIPT.setLocation(new ClassPathResource("/lua/desc_stock.lua"));    // 設(shè)置返回值類型    DECREASE_GOOD_STOCK_SCRIPT.setResultType(Long.class);}@PostMapping("/lua")public Map<String, Object> goodDescControllerByLUA(Long id) {    List<String> keys = new ArrayList<>();    keys.add("stock:" + id);    HashMap<String, Object> ret = new HashMap<>();    ret.put("info", "fail, 扣減失敗");    Long execute = redisTemplate.execute(DECREASE_GOOD_STOCK_SCRIPT, keys);    if (execute == 1) {goodMapper.descGoodStock(id);ret.put("info", "success, 扣減成功");    }    return ret;}

    結(jié)果:Redis 和 MySQL 中的庫存均為 0 ,沒有超賣

    使用分布式鎖

    可以使用 redisson 分布式鎖進行扣減庫存處理,鎖住查詢和扣減兩個步驟即可;

    若是在分布式環(huán)境下,要考慮 分布式鎖 與 LUA 腳本的結(jié)合!

    java 代碼

    @PostMapping("/lock")public Map<String, Object> goodDescControllerByLock(Long id) throws InterruptedException {    HashMap<String, Object> ret = new HashMap<>();    ret.put("info", "fail, 扣減失敗");    // 加鎖    RLock lock = redissonClient.getLock("stock" + id);    boolean tryLock = lock.tryLock(2L, 1L, TimeUnit.SECONDS);    if (tryLock) {Integer stock = (Integer) redisTemplate.opsForValue().get(key + id);if (stock <= 0) {    return ret;}Long decrement = redisTemplate.opsForValue().decrement(key + id);if (decrement >= 0) {    goodMapper.descGoodStock(id);    ret.put("info", "success, 扣減成功");}    }    return ret;}

    測試結(jié)果

    Redis 中庫存數(shù)量沒有超賣

    MySQL 中庫存數(shù)量沒有超賣

    總結(jié)

    如果在項目初期流量較少可以考慮基于 數(shù)據(jù)庫行鎖 進行庫存的扣減,到了后期流量大,幾乎都要用到 Redis:

    • decrement:追求簡單快速實現(xiàn),不考慮 Redis 庫存中的準確性;
    • LUA 腳本:追求 Redis 中庫存的準確性,在 Redis 層面上要進行多重的條件判斷
    • Lock:追求 Redis 中庫存的準確性,在分布式環(huán)境中要考慮 LUA + Lock 的結(jié)合

    到此這篇關(guān)于基于MySQL和Redis扣減庫存的實踐的文章就介紹到這了,更多相關(guān)MySQL和Redis扣減庫存內(nèi)容請搜索以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持!

    標簽: MySQL
    日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
    五月天久久777| 夜夜嗨网站十八久久| 国产精品久久免费视频| 欧美亚洲在线日韩| 欧美gv在线| 999久久久精品国产| 国产66精品| 牛牛精品成人免费视频| 国产精品对白久久久久粗| 伊人影院久久| 日韩精品视频一区二区三区| 免费一区二区三区在线视频| 精品一区二区三区中文字幕在线| 日韩av网站在线观看| 欧美视频久久| 精品视频亚洲| 日韩av网站在线观看| 亚洲免费影院| 免费人成精品欧美精品| 丝袜诱惑制服诱惑色一区在线观看| 青草国产精品| 欧美精品羞羞答答| 久久超碰99| 日本成人在线不卡视频| 欧美日韩精品免费观看视频完整| 欧美激情视频一区二区三区免费| 蜜桃av一区| 黄色av日韩| 国产aa精品| 日本免费新一区视频| 先锋影音久久久| 91精品精品| 亚洲伊人av| 老牛影视精品| 精品亚洲二区| 国产精品亚洲人成在99www| 久久亚洲国产精品一区二区| 成人免费网站www网站高清| 麻豆成人综合网| 欧美日韩黑人| 久久国产精品免费精品3p| 久久精品av| 激情欧美一区| 高清一区二区| 狠狠久久伊人| 免费福利视频一区二区三区| 成人在线丰满少妇av| 日本久久精品| 午夜精品成人av| 亚洲不卡系列| 欧美成人综合| 亚洲深深色噜噜狠狠爱网站| 综合激情五月婷婷| 日本成人中文字幕| 国产精区一区二区| 国产一区二区三区免费在线| 国产中文在线播放| 欧美aa国产视频| 99国产精品私拍| 亚洲伊人影院| 国产高清精品二区| 久久一区欧美| 国产精品一页| 九九久久国产| 日本一区二区高清不卡| 色网在线免费观看| www.51av欧美视频| 久久久久国产精品一区三寸| 久久久久久久久99精品大| 香蕉国产精品| 国产午夜久久av| 六月天综合网| 免费欧美一区| 国产欧美视频在线| 亚洲欧美日韩高清在线| 久久xxx视频| 日韩av三区| 亚洲综合图色| 蜜桃久久精品一区二区| 黄色成人在线网址| 欧洲精品一区二区三区| 六月丁香综合在线视频| 日韩一区二区三区免费| 日韩国产在线| 999久久久精品国产| 999久久久精品国产| 欧美成人综合| 成人亚洲一区| 日本成人在线一区| 久久国产精品99国产| 999国产精品| 久久理论电影| 国产精品久久久久蜜臀 | 视频一区日韩精品| 亚洲午夜久久久久久尤物| www.com.cn成人| 精品美女在线视频| 婷婷综合社区| 久久精品亚洲| 一区二区不卡| 在线日韩一区| 日韩和欧美的一区| av不卡在线看| 亚洲黄色免费av| 奇米色欧美一区二区三区| 日韩av一级| 久久亚洲黄色| 欧美亚洲色图校园春色| 午夜视频精品| 久久久久久免费视频| 你懂的国产精品永久在线| 亚洲精品在线国产| 黑丝一区二区| 亚洲成人va| 国产一区二区亚洲| 国产精品片aa在线观看| 欧美不卡高清| 99国产精品免费视频观看| 欧美极品一区二区三区| 婷婷精品在线| 综合激情视频| 日韩精品欧美成人高清一区二区| 91久久中文| 午夜一级久久| 国产一区成人| 免费看的黄色欧美网站| 日韩一级欧洲| 鲁大师成人一区二区三区| 国产视频久久| 亚洲尤物av| 欧美一区二区三区久久| 久久亚洲一区| 日本精品国产| 日韩av资源网| 国产精品午夜一区二区三区| 国产精品视频一区二区三区四蜜臂| 日韩av资源网| 久久久久黄色| 日韩久久精品网| 国产精品普通话对白| 日韩动漫一区| 国产aⅴ精品一区二区三区久久| 亚洲精品一区二区在线播放∴| 国产精品色在线网站| 99视频精品全国免费| 亚洲精品精选| 国产极品模特精品一二| 欧美成人基地| 免费人成网站在线观看欧美高清| 国产高清精品二区| 欧美成人午夜| 国产亚洲一卡2卡3卡4卡新区| jiujiure精品视频播放| 国产精品大片| 红桃视频国产精品| 日韩在线不卡| 欧美中文一区| 狠狠爱成人网| 一区二区三区四区日本视频| 在线一区二区三区视频| 福利精品在线| 久久av一区| 91精品蜜臀一区二区三区在线| 日韩av一区二| 午夜在线一区| 亚洲精品在线观看91| 日韩免费福利视频| 国产日韩一区二区三区在线| 国产美女精品| 999久久久精品国产| 国产在线看片免费视频在线观看| 国产欧美在线| 国产欧美一区二区三区国产幕精品| 久久电影一区| 欧洲毛片在线视频免费观看| 日韩在线欧美| 国产精品三级| 欧美黄色精品| 国产成人精选| 久久精品国产网站| 另类综合日韩欧美亚洲| 久久精品国产亚洲一区二区三区| 国产精品一区高清| 久久中文精品| 日韩综合精品| 亚洲黄色影院| 日韩中文字幕一区二区三区| 亚洲在线免费| 日韩av一二三| 国产精品网站在线看| 国产精品男女| 久久国产日韩欧美精品| 麻豆高清免费国产一区| 日韩免费av| 欧美日韩国产在线观看网站 | 日韩和欧美一区二区三区| 中文精品视频| 欧美精品羞羞答答| 欧美国产专区| 黄色免费成人|