最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

微服務(wù)Spring?Boot?整合Redis?阻塞隊列實現(xiàn)異步秒殺下單思路詳解

 更新時間:2022年10月31日 10:15:51   作者:Bug?終結(jié)者  
這篇文章主要介紹了微服務(wù)Spring?Boot?整合Redis?阻塞隊列實現(xiàn)異步秒殺下單,使用阻塞隊列實現(xiàn)秒殺的優(yōu)化,采用異步秒殺完成下單的優(yōu)化,本文給大家分享詳細步驟及實現(xiàn)思路,需要的朋友可以參考下

?引言

本章節(jié),介紹使用阻塞隊列實現(xiàn)秒殺的優(yōu)化,采用異步秒殺完成下單的優(yōu)化

一、秒殺優(yōu)化 - 異步秒殺思路

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

  • 查詢優(yōu)惠卷
  • 判斷秒殺庫存是否足夠
  • 查詢訂單
  • 校驗是否是一人一單
  • 扣減庫存
  • 創(chuàng)建訂單,完成

在以上6個步驟中,我們可以采用怎樣的方式來優(yōu)化呢?

整體思路:當(dāng)用戶下單之后,判斷庫存是否充足只需要導(dǎo)redis中去根據(jù)key找對應(yīng)的value是否大于0即可,如果不充足,則直接結(jié)束,如果充足,繼續(xù)在redis中判斷用戶是否可以下單,如果set集合中沒有這條數(shù)據(jù),說明他可以下單,如果set集合中沒有這條記錄,則將userId和優(yōu)惠卷存入到redis中,并且返回0,整個過程需要保證是原子性的,我們可以使用Lua來操作

當(dāng)以上邏輯走完后,我們可以根據(jù)返回的結(jié)果來判斷是否是0,如果是0,則可以下單,可以存入 queue 隊列中,然后返回,前端可以通過返回的訂單id來判斷是否下單成功。

二、秒殺優(yōu)化 - 基于Redis完成秒殺資格判斷

需求:

  • 新增秒殺優(yōu)惠卷的同時,需要將優(yōu)惠卷信息保存在redis中
  • 基于Lua腳本實現(xiàn),判斷秒殺庫存、一人一單,決定用戶是否搶購成功
  • 如果搶購成功,將優(yōu)惠卷id和用戶id封裝后存入阻塞隊列
  • 開啟線程任務(wù),不斷從阻塞隊列中獲取信息,實現(xiàn)異步下單功能

新增優(yōu)惠卷時,將優(yōu)惠卷信息存入Redis

VoucherService

@Override
    @Transactional
    public void addSeckillVoucher(Voucher voucher) {
        // 保存優(yōu)惠券
        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
        stringRedisTemplate.opsForValue().set(RedisConstants.SECKILL_STOCK_KEY + voucher.getId(), voucher.getStock().toString());
    }

新增優(yōu)惠卷時,可存入redis信息

編寫 Lua 腳本,實現(xiàn)秒殺資格判斷

seckill Lua 秒殺腳本

-- 1.參數(shù)列表
-- 1.1 優(yōu)惠卷id
local voucherId = ARGV[1]
-- 1.2 用戶id
local userId = ARGV[2]

-- 2. 數(shù)據(jù)key
-- 2.1 庫存key 拼接 ..
local stockKey = 'seckill:stock:' .. voucherId
-- 2.2 訂單key 拼接 ..
local orderKey = "seckill:order" .. voucherId

-- 3. 腳本業(yè)務(wù)
-- 3.1 判斷庫存是否充足
if (tonumber(redis.call('get', stockKey)) <= 0) then
    -- 3.2 庫存不足
    return 1
end
-- 3.2 判斷用戶是否下單 SISMEMBER orderKey userId
if (redis.call('sismember', orderKey, userId) == 1) then
    -- 3.3 存在,證明是重復(fù)下單
    return 2
end
-- 3.4 扣庫存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
-- 3.5 下單 保存用戶 sadd orderKey userId
redis.call('sadd', orderKey, userId)
return 0

三、基于阻塞隊列完成異步秒殺下單

基于阻塞隊列實現(xiàn)異步秒殺下單

核心思路:將請求存入阻塞隊列中 進行緩存,開啟線程池讀取任務(wù)并依次處理。

VoucherOrderService

	
	private static final DefaultRedisScript<Long> SECKILL_SCRIPT;

    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }


    private BlockingQueue<VoucherOrder> orderTasks =new ArrayBlockingQueue<>(1024 * 1024);
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

    //項目啟動后執(zhí)行該方法
    @PostConstruct
    private void init() {
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());
    }

    // 用于線程池處理的任務(wù)
    // 當(dāng)初始化完畢后 就會去從對列中去拿信息
    private class VoucherOrderHandler implements Runnable {

        @Override
        public void run() {
            while (true){
                try {
                    // 1.獲取隊列中的訂單信息
                    VoucherOrder voucherOrder = orderTasks.take();
                    // 2.創(chuàng)建訂單
                    handleVoucherOrder(voucherOrder);
                } catch (Exception e) {
                    log.error("處理訂單異常", e);
                }
            }
        }
    }

    private void handleVoucherOrder(VoucherOrder voucherOrder) {
        //1.獲取用戶
        Long userId = voucherOrder.getUserId();
        // 2.創(chuàng)建鎖對象
        RLock lock = redissonClient.getLock("lock:order:" + userId);
        // 3.嘗試獲取鎖
        boolean isLock = lock.tryLock();
        // 4.判斷是否獲得鎖成功
        if (!isLock) {
            // 獲取鎖失敗,直接返回失敗或者重試
            log.error("不允許重復(fù)下單!");
            return;
        }
        try {
            //注意:由于是spring的事務(wù)是放在threadLocal中,此時的是多線程,事務(wù)會失效
            proxy.createVoucherOrder(voucherOrder);
        } finally {
            // 釋放鎖
            lock.unlock();
        }
    }


    // 代理對象
    private IVoucherOrderService proxy;

    @Override
    public Result seckillVoucher(Long voucherId) {
        // 獲取用戶
        Long userId = UserHolder.getUser().getId();

        // 獲取訂單id
        long orderId = redisIdWorker.nextId("order");

        // 1. 執(zhí)行l(wèi)ua 腳本
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(),
                userId.toString(), String.valueOf(orderId)
        );

        int r = result.intValue();

        // 2. 判斷結(jié)果是否為0
        if (r != 0) {
            // 2.1 不為0 代表沒有購買資格
            return Result.fail(r == 1 ? "庫存不足" : "不允許重復(fù)下單");
        }
        // 2.2 為0,有購買資格 把下單信息保存到阻塞隊列
        // 2.2 有購買的資格,創(chuàng)建訂單放入阻塞隊列中
        VoucherOrder voucherOrder = new VoucherOrder();
        // 2.3.訂單id
        voucherOrder.setId(orderId);
        // 2.4.用戶id
        voucherOrder.setUserId(userId);
        // 2.5.代金券id
        voucherOrder.setVoucherId(voucherId);
        // 2.6.放入阻塞隊列
        orderTasks.add(voucherOrder);
        //3.獲取代理對象
        proxy = (IVoucherOrderService)AopContext.currentProxy();
        // 2.3 返回訂單id

        return Result.ok(orderId);
    }


    @Transactional
    public void createVoucherOrder (VoucherOrder voucherOrder){
        // 5.一人一單邏輯
        // 5.1.用戶id
        Long userId = voucherOrder.getUserId();

        // 判斷是否存在
        int count = query().eq("user_id", userId)
                .eq("voucher_id", voucherOrder.getId()).count();

        // 5.2.判斷是否存在
        if (count > 0) {
            // 用戶已經(jīng)購買過了
            log.error("用戶已經(jīng)購買過了");
        }

        //6,扣減庫存
        boolean success = seckillVoucherService.update()
                .setSql("stock= stock -1") //set stock = stock -1
                .eq("voucher_id", voucherOrder.getVoucherId()).gt("stock",0).update(); //where id = ? and stock > 0
        // .eq("voucher_id", voucherId).eq("stock",voucher.getStock()).update(); //where id = ? and stock = ?

        if (!success) {
            //扣減庫存
            log.error("庫存不足!");
        }

        save(voucherOrder);
    }

四、測試程序

ApiFox 測試程序

測試成功,查看Redis

成功添加訂單信息

庫存信息

數(shù)據(jù)庫信息

Jmeter 進行壓力測試

恢復(fù)數(shù)據(jù),進行壓力測試

關(guān)于測試:新增了1000條用戶信息,存入數(shù)據(jù)庫和Redis,token,Jmeter使用Tokens文件測試1000條并發(fā)

相關(guān)資料見下文

進行壓測

經(jīng)過檢測,性能提升了幾十倍!

數(shù)據(jù)庫

五、源碼地址

Jmeter測試文件:https://www.bilibili.com/video/av251263036/

以上就是【Bug 終結(jié)者】對 微服務(wù)Spring Boot 整合Redis 阻塞隊列實現(xiàn)異步秒殺下單 的簡單介紹,在分布式系統(tǒng)下,高并發(fā)的場景下,使用阻塞隊列來優(yōu)化秒殺下單,但依舊不是最優(yōu)解,持續(xù)更新中!下章節(jié) 采用消息隊列優(yōu)化秒殺下單!

到此這篇關(guān)于微服務(wù)Spring Boot 整合Redis 阻塞隊列實現(xiàn)異步秒殺下單的文章就介紹到這了,更多相關(guān)Spring Boot 整合Redis 阻塞隊列內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Java中的數(shù)組與字符串相關(guān)知識

    詳解Java中的數(shù)組與字符串相關(guān)知識

    這篇文章主要介紹了詳解Java中的數(shù)組與字符串相關(guān)知識,包括操作字符串的一些基本方法列舉,需要的朋友可以參考下
    2015-09-09
  • 詳解mybatis #{}和${}的區(qū)別、傳參、基本語法

    詳解mybatis #{}和${}的區(qū)別、傳參、基本語法

    這篇文章主要介紹了mybatis #{}和${}的區(qū)別、傳參、基本語法,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • 使用RestTemplate調(diào)用RESTful?API的代碼示例

    使用RestTemplate調(diào)用RESTful?API的代碼示例

    在開發(fā)?Web?應(yīng)用程序時,調(diào)用?RESTful?API?是一個常見的任務(wù),本文將介紹如何使用?RestTemplate?調(diào)用?RESTful?API,并提供示例代碼,感興趣的同學(xué)可以跟著小編一起來看看
    2023-06-06
  • 解決springboot的findOne方法沒有合適的參數(shù)使用問題

    解決springboot的findOne方法沒有合適的參數(shù)使用問題

    這篇文章主要介紹了解決springboot的findOne方法沒有合適的參數(shù)使用問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • IDEA連接達夢數(shù)據(jù)庫的詳細配置指南

    IDEA連接達夢數(shù)據(jù)庫的詳細配置指南

    達夢數(shù)據(jù)庫(DM Database)作為國產(chǎn)關(guān)系型數(shù)據(jù)庫的代表,廣泛應(yīng)用于企業(yè)級系統(tǒng)開發(fā),本文將詳細介紹如何在IntelliJ IDEA中配置并連接達夢數(shù)據(jù)庫,助力開發(fā)者高效完成數(shù)據(jù)庫開發(fā)工作,需要的朋友可以參考下
    2025-03-03
  • Java8新特性Stream短路終端操作實例解析

    Java8新特性Stream短路終端操作實例解析

    這篇文章主要介紹了Java8新特性Stream短路終端操作實例解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-12-12
  • Java中JSONObject與JSONArray的使用區(qū)別詳解

    Java中JSONObject與JSONArray的使用區(qū)別詳解

    這篇文章主要介紹了Java中JSONObject與JSONArray的使用區(qū)別詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-11-11
  • SpringBoot實現(xiàn)單元測試示例詳解

    SpringBoot實現(xiàn)單元測試示例詳解

    單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證。這篇文章主要為大家介紹了C語言實現(xiàn)單元測試的方法,需要的可以參考一下
    2022-11-11
  • JAVA 對50取余數(shù)的五種方法試下

    JAVA 對50取余數(shù)的五種方法試下

    在數(shù)學(xué)計算中經(jīng)常會遇到余數(shù),本文主要介紹了JAVA 對50取余數(shù)的五種方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-03-03
  • Java中的自旋鎖與阻塞鎖詳解

    Java中的自旋鎖與阻塞鎖詳解

    這篇文章主要介紹了Java中的自旋鎖與阻塞鎖詳解,阻塞鎖是指當(dāng)線程嘗試獲取鎖失敗時,線程進入阻塞狀態(tài),直到接收信號后被喚醒,阻塞或者喚醒一個Java線程需要操作系統(tǒng)切換CPU?狀態(tài)來完成,這種狀態(tài)轉(zhuǎn)換?需要耗費處理器時間,需要的朋友可以參考下
    2023-10-10

最新評論

高密市| 洮南市| 万山特区| 曲沃县| 财经| 文安县| 兰溪市| 汶川县| 武宁县| 珠海市| 商丘市| 外汇| 娄底市| 蓬安县| 肥城市| 岳阳县| 陵川县| 贺州市| 拜城县| 长宁区| 县级市| 溧水县| 沾化县| 独山县| 丁青县| 莆田市| 探索| 徐水县| 霍山县| 青州市| 滕州市| 东莞市| 湛江市| 沾益县| 阳新县| 桐庐县| 四子王旗| 孟州市| 应城市| 黑山县| 海淀区|