软件系统定制开发微服务Spring Boot 整合Redis 阻塞队列实现异步秒杀下单

文章目录

⛅引言

本章节,介绍软件系统定制开发使用阻塞队列实现秒杀的优化,软件系统定制开发采用异步秒杀完成下单的优化

一、秒杀优化 - 软件系统定制开发异步秒杀思路

软件系统定制开发当用户发起请求,此时会请求nginx,nginx会访问到tomcat,而tomcat中的程序,软件系统定制开发会进行串行操作,软件系统定制开发分成如下几个步骤

  1. 软件系统定制开发查询优惠卷
  2. 软件系统定制开发判断秒杀库存是否足够
  3. 查询订单
  4. 校验是否是一人一单
  5. 扣减库存
  6. 创建订单,完成

在以上6个步骤中,我们可以采用怎样的方式来优化呢?

整体思路:当用户下单之后,判断库存是否充足只需要导redis中去根据key找对应的value是否大于0即可,如果不充足,则直接结束,如果充足,继续在redis中判断用户是否可以下单,如果set集合中没有这条数据,说明他可以下单,如果set集合中没有这条记录,则将userId和优惠卷存入到redis中,并且返回0,整个过程需要保证是原子性的,我们可以使用Lua来操作

当以上逻辑走完后,我们可以根据返回的结果来判断是否是0,如果是0,则可以下单,可以存入 queue 队列中,然后返回,前端可以通过返回的订单id来判断是否下单成功。

二、秒杀优化 - 基于Redis完成秒杀资格判断

需求:

  • 新增秒杀优惠卷的同时,需要将优惠卷信息保存在redis中
  • 基于Lua脚本实现,判断秒杀库存、一人一单,决定用户是否抢购成功
  • 如果抢购成功,将优惠卷id和用户id封装后存入阻塞队列
  • 开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能

新增优惠卷时,将优惠卷信息存入Redis

VoucherService

@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        stringRedisTemplate.opsForValue().set(RedisConstants.SECKILL_STOCK_KEY + voucher.getId(), voucher.getStock().toString());    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

新增优惠卷时,可存入redis信息

编写 Lua 脚本,实现秒杀资格判断

seckill Lua 秒杀脚本

-- 1.参数列表-- 1.1 优惠卷idlocal voucherId = ARGV[1]-- 1.2 用户idlocal userId = ARGV[2]-- 2. 数据key-- 2.1 库存key 拼接 ..local stockKey = 'seckill:stock:' .. voucherId-- 2.2 订单key 拼接 ..local orderKey = "seckill:order" .. voucherId-- 3. 脚本业务-- 3.1 判断库存是否充足if (tonumber(redis.call('get', stockKey)) <= 0) then    -- 3.2 库存不足    return 1end-- 3.2 判断用户是否下单 SISMEMBER orderKey userIdif (redis.call('sismember', orderKey, userId) == 1) then    -- 3.3 存在,证明是重复下单    return 2end-- 3.4 扣库存 incrby stockKey -1redis.call('incrby', stockKey, -1)-- 3.5 下单 保存用户 sadd orderKey userIdredis.call('sadd', orderKey, userId)return 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

三、基于阻塞完成异步秒杀下单

基于阻塞队列实现异步秒杀下单

核心思路:将请求存入阻塞队列中 进行缓存,开启线程池读取任务并依次处理。

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();    //项目启动后执行该方法    @PostConstruct    private void init() {        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());    }    // 用于线程池处理的任务    // 当初始化完毕后 就会去从对列中去拿信息    private class VoucherOrderHandler implements Runnable {        @Override        public void run() {            while (true){                try {                    // 1.获取队列中的订单信息                    VoucherOrder voucherOrder = orderTasks.take();                    // 2.创建订单                    handleVoucherOrder(voucherOrder);                } catch (Exception e) {                    log.error("处理订单异常", e);                }            }        }    }    private void handleVoucherOrder(VoucherOrder voucherOrder) {        //1.获取用户        Long userId = voucherOrder.getUserId();        // 2.创建锁对象        RLock lock = redissonClient.getLock("lock:order:" + userId);        // 3.尝试获取锁        boolean isLock = lock.tryLock();        // 4.判断是否获得锁成功        if (!isLock) {            // 获取锁失败,直接返回失败或者重试            log.error("不允许重复下单!");            return;        }        try {            //注意:由于是spring的事务是放在threadLocal中,此时的是多线程,事务会失效            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. 执行lua 脚本        Long result = stringRedisTemplate.execute(                SECKILL_SCRIPT,                Collections.emptyList(),                voucherId.toString(),                userId.toString(), String.valueOf(orderId)        );        int r = result.intValue();        // 2. 判断结果是否为0        if (r != 0) {            // 2.1 不为0 代表没有购买资格            return Result.fail(r == 1 ? "库存不足" : "不允许重复下单");        }        // 2.2 为0,有购买资格 把下单信息保存到阻塞队列        // 2.2 有购买的资格,创建订单放入阻塞队列中        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) {            // 用户已经购买过了            log.error("用户已经购买过了");        }        //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);    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135

四、测试程序

ApiFox 测试程序

测试成功,查看Redis

成功添加订单信息

库存信息

数据库信息

Jmeter 进行压力测试

恢复数据,进行压力测试

关于测试:新增了1000条用户信息,存入数据库和Redis,token,Jmeter使用Tokens文件测试1000条并发

相关资料见下文

进行压测

经过检测,性能提升了几十倍!

数据库

五、源码地址

源码地址及 Jmeter测试文件: 公众号进行获取网盘地址,后续我会上传至百度网盘

⛵小结

以上就是【Bug 终结者】对 微服务Spring Boot 整合Redis 阻塞队列实现异步秒杀下单 的简单介绍,在分布式系统下,高并发的场景下,使用阻塞队列来优化秒杀下单,但依旧不是最优解,持续更新中!下章节 采用消息队列优化秒杀下单!

如果这篇【文章】有帮助到你,希望可以给【Bug 终结者】点个赞👍,创作不易,如果有对【后端技术】、【前端领域】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【Bug 终结者】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💝💝💝!

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发