Redis实战——秒杀业务优化

news2025/1/17 18:15:24

我们来回顾一下下单流程

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

1、查询优惠卷

2、判断秒杀库存是否足够

3、查询订单

4、校验是否是一人一单

5、扣减库存

6、创建订单

之前的秒杀业务流程图:

在这六步操作中,又有很多操作是要去操作数据库的,而且还是一个线程串行执行, 这样就会导致我们的程序执行的很慢,所以我们需要异步程序执行,那么如何加速呢?

优化方案

我们将耗时比较短的逻辑判断放入到redis中,比如是否库存足够,比如是否一人一单,这样的操作,只要这种逻辑可以完成,就意味着我们是一定可以下单完成的,我们只需要进行快速的逻辑判断,根本就不用等下单逻辑走完,我们直接给用户返回成功而后再在后台开一个线程后台线程慢慢的去执行queue(队列)里边的消息,完成创建订单的动作(异步操作创建订单)。这样程序不就超级快了吗?而且也不用担心线程池消耗殆尽的问题,因为这里我们的程序中并没有手动使用任何线程池。

具体实现流程:

首先,在Reids中进行判断是否符合条件,我们可以使用redis中list类型的方法存入用户信息,key订单key,value就传入用户id(多个,所以使用list类型方法)并且在redis中存入库存信息

然后编写Lua脚本实现业务逻辑,实现判断用户是否有资格下单。

逻辑参考下图。

 编写完Lua脚本后,就可以在Java中执行对于的业务逻辑了

1. 先调用Lua脚本

2.判断结果

3.不为0,返回错误

4.为0,用户符合条件,将订单信息传入到阻塞队列中,并开启线程异步创建订单

5.然后订单信息

具体流程图如下:

当然这里边有两个难点、

第一个难点是我们怎么在redis中去快速校验一人一单,还有库存判断?

第二个难点是如何将订单信息放入到阻塞队列中,并创建一个新的线程异步创建订单?

对于使用Redis中去快速校验一人一单,还有库存判断,上述已经讲过,就是使用Lua脚本

具体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.库存不足,返回1
    return 1
end
-- 3.2.判断用户是否下单 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then
    -- 3.3.存在,说明是重复下单,返回2
    return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
-- 3.5.下单(保存用户)sadd orderKey userId
redis.call('sadd', orderKey, userId)
-- 3.6.发送消息到队列中, XADD stream.orders * k1 v1 k2 v2 ...
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
return 0

然后就是将订单信息放入到阻塞队列中,并创建一个新的线程异步创建订单

温馨提示:这只是一个伪代码~ 具体代码实现在最下方

我们可以使用下述代码创建一个阻塞队列和异步创建订单

创建阻塞队列:

BlockingQueue<VoucherOrder> blockingQueue = new ArrayBlockingQueue<>(1024*1024)
//保存订单信息
blockingQueue.add(voucherOrder);
//获取订单信息
blockingQueue.tack();

异步创建订单: 

    BlockingQueue<VoucherOrder> blockingQueue = new ArrayBlockingQueue<>(1024*1024)
    //获取线程池
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

 //在类初始化的是否就执行异步下单的任务
    @PostConstruct
    public void init(){
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandle());
    }

    //执行异步下单的任务
    private class VoucherOrderHandle implements Runnable{

        @Override
        public void run() {
            while (true){
                try {
                    //1.获取队列中的订单信息
                    VoucherOrder voucherOrder = blockingQueue.take();
                    //2.创建订单
                    handlerVoucherOrder(voucherOrder);
                } catch (InterruptedException e) {
                    log.error("获取订单信息异常{}",e);
                }
            }
        }
    }

完整代码:

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.库存不足,返回1
    return 1
end
-- 3.2.判断用户是否下单 SISMEMBER orderKey userId
if(redis.call('sismember', orderKey, userId) == 1) then
    -- 3.3.存在,说明是重复下单,返回2
    return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
-- 3.5.下单(保存用户)sadd orderKey userId
redis.call('sadd', orderKey, userId)
-- 3.6.发送消息到队列中, XADD stream.orders * k1 v1 k2 v2 ...
redis.call('xadd', 'stream.orders', '*', 'userId', userId, 'voucherId', voucherId, 'id', orderId)
return 0

Java代码: 

package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
@Slf4j
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService iSeckillVoucherService;

    @Resource
    private RedisIdWorker redisIdWorker;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private RedissonClient redissonClient;

    //获取lua脚本
    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> blockingQueue = new ArrayBlockingQueue<>(1024*1024);

    //获取线程池
    private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();

    //在类初始化的是否就执行异步下单的任务
    @PostConstruct
    public void init(){
        SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandle());
    }

    //执行异步下单的任务
    private class VoucherOrderHandle implements Runnable{

        @Override
        public void run() {
            while (true){
                try {
                    //1.获取队列中的订单信息
                    VoucherOrder voucherOrder = blockingQueue.take();
                    //2.创建订单
                    handlerVoucherOrder(voucherOrder);
                } catch (InterruptedException e) {
                    log.error("获取订单信息异常{}",e);
                }
            }
        }
    }

    private void handlerVoucherOrder(VoucherOrder voucherOrder) {
        //获取用户id
        Long userId = voucherOrder.getUserId();
        //1.创建锁对象
        //SimpleRedisLock lock = new SimpleRedisLock(stringRedisTemplate, "order:" + userId);
        RLock lock = redissonClient.getLock("order:" + userId);

        //2.尝试获取锁
        boolean isLock = lock.tryLock();
        if (!isLock){
            //获取锁失败
            log.error("获取锁失败");
            return;
        }
        try {
             proxy.createVoucherOrder(voucherOrder);
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    private IVoucherOrderService proxy;

    @Override
    public Result seckillVoucher(Long voucherId) {
        Long userId = UserHolder.getUser().getId();

        //1.执行lua脚本
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                voucherId.toString(), userId.toString()
        );
        //2.判断返回结果是否为0
        int r = result.intValue();
        if (r != 0) {
            //3.如果不为0,代表没有下单资格
            Result.fail(r==1?"库存不足!":"不可重复下单!");
        }

        //4.如果为0,有购买资格,把下单信息保存到阻塞队列
        long order = redisIdWorker.nextId("order");
        //4.1 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //4.2添加订单id
        Long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //4.3添加用户id
        voucherOrder.setUserId(userId);
        //4.3添加优惠券id
        voucherOrder.setVoucherId(voucherId);
        blockingQueue.add(voucherOrder);

        //5.获取代理对象
        proxy = (IVoucherOrderService) AopContext.currentProxy();
        //6.返回一个订单id
        return Result.ok(order);
    }


    @Transactional
    public void createVoucherOrder(VoucherOrder voucherOrder) {
        Long userId = voucherOrder.getUserId();
        //6.根据优惠券id和用户id判断订单是否已经存在
        //如果存在,则返回错误信息
        int count = query().eq("user_id", userId).eq("voucher_id", voucherOrder.getVoucherId()).count();
        if (count > 0) {
            log.error("用户已经购买!");
            return;
        }
        boolean success = iSeckillVoucherService.update()
                .setSql("stock=stock-1")
                .eq("voucher_id", voucherOrder.getVoucherId()).gt("stock", 0)
                .update();
        if (!success){
            //扣减失败
            log.error("扣减失败");
            return;
        }
        save(voucherOrder);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/81414.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

新型海上风电机组及压缩空气储能系统的建模与控制(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f468;‍&#x1f393;博主课外兴趣&#xff1a;中西方哲学&#xff0c;送予读者&#xff1a; &#x1f468;‍&a…

node+vue基于微信小程序的货物管理系统 计算机毕业设计

随着Internet的发展&#xff0c;人们的日常生活已经离不开网络。未来人们的生活与工作将变得越来越数字化、网络化和电子化。本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采用java语言技术、node框架和微信小程序来完成对系统的设计。…

CookieSession 学习笔记

1 Cookie 1.1 Cookie的基本使用 1 概念 客户端会话技术&#xff0c;将数据保存到客户端&#xff0c;以后每次请求都携带Cookie数据请求 2 工作流程 服务端Servlet可以将给response设置Cookie,这样浏览器接收到的数据中&#xff0c;就含有Cookie数据,下次请求时&#xff0c;会…

大数据期末课设~电商网站日志数据分析

目录 一、背景介绍... 1 二、大数据平台架构设计... 2 三、大数据平台系统设计... 7 四、数据分析与达成目标... 11 五、Spark综合编程与python可视化... 33 六、总结与体会... 50 一、背景介绍 一般情况下&#xff0c;大数据平台指的是使用了Hadoop、Spark、Storm、Fli…

可交易性(tradability)检验即协整性检验:线性关系

两个时间序列的线性关系表示为&#xff1a; 左边是两个时间序列的线性组合。是协整系数。 右边是残差序列&#xff08;residual series),表示为由两部分组成。是均衡值&#xff08;equilibrium value&#xff09;&#xff0c;是一个均值为0的时间序列&#xff0c;可以构造为均…

系统 CPU 突然飙升且 GC 频繁,如何排查

处理过线上问题的同学基本上都会遇到系统突然运行缓慢&#xff0c;CPU 100%&#xff0c;以及Full GC次数过多的问题。 当然&#xff0c;这些问题的最终导致的直观现象就是系统运行缓慢&#xff0c;并且有大量的报警。 本文主要针对系统运行缓慢这一问题&#xff0c;提供该问题…

R语言中的岭回归、套索回归、主成分回归:线性模型选择和正则化

概述和定义 在本文中&#xff0c;我们将考虑一些线性模型的替代拟合方法&#xff0c;除了通常的 普通最小二乘法。这些替代方法有时可以提供更好的预测准确性和模型可解释性。最近我们被客户要求撰写关于模型选择的研究报告&#xff0c;包括一些图形和统计输出。 主成分分析P…

19. Dropout从零代码实现以及简洁实现

1. 从零实现 要实现单层的暂退法函数&#xff0c; 我们从均匀分布U[0,1]中抽取样本&#xff0c;样本数与这层神经网络的维度一致。 然后我们保留那些对应样本大于p的节点&#xff0c;把剩下的丢弃。 在下面的代码中&#xff0c;我们实现 dropout_layer 函数&#xff0c; 该函…

【统一融合:U2Fusion】

U2Fusion: A Unified Unsupervised Image Fusion Network &#xff08;U2Fusion&#xff1a;一种统一的无监督图像融合网络&#xff09; 研究提出了一种新颖的统一监督和管理端到端图像融合网络,称为U2Fusion,能够解决不同的融合问题,包括多模态,多曝光,和多聚焦融合。利用特征…

网络0323和网络2303分类过程的比较

( A, B )---2*30*2---( 1, 0 )( 0, 1 ) 用网络分类A和B&#xff0c;让A是&#xff08;0&#xff0c;0&#xff09;&#xff08;1&#xff0c;1&#xff09;&#xff0c;让B是&#xff08;1&#xff0c;0&#xff09;&#xff08;1&#xff0c;1&#xff09;。测试集均为&#…

MySQL学习记录(8)MySQL锁

5、锁 5.1、概述 ​ 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、 RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有 效性是所有数据库必…

数字经济2023前瞻

判断1:“超个性化”已成数字化核心诉求 判断2:瞄准“既要又要还要”,业务与技术逼近“同心圆” 判断3:数据要素市场化配置的序幕拉开 判断4:产业数字化技术开源,打造深水区的群智协同平台 判断5:传统龙头企业的数字化“溢出”,实现赚钱传承双丰收 判断6:工业互联网将成…

Python学习----property属性上下文管理器生成器深拷贝浅拷贝正则表达式

property属性 property 属性就是负责把类中的一个方法当作属性使用&#xff0c;这样可以简化代码使用。 定义property属性有两种方式 1、装饰器方式 2、类属性方式 装饰器方式&#xff1a; 原本私有属性&#xff0c;我们不能直接访问&#xff0c;只能通过方法简介进行访问。…

网络安全观察报告 惯犯观察

执行摘要 从 1987 年 9 月 14 日&#xff0c;中国向世界发出第一封电子邮件到如今&#xff0c;中国的互联网发展已过去整整 31 个年头。从消费互联、产业互联到万物互联&#xff0c;互联网正在加速改变我们的交流方式和交易方式&#xff0c;一次次 004.重塑了国家的经济形态和…

[附源码]Python计算机毕业设计宠物用品购物网站Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

springboot+vue基本微信小程序的校园二手物品交易平台系统

在大学校园里&#xff0c;存在着很多的二手商品&#xff0c;但是由于信息资源的不流通以及传统二手商品信息交流方式的笨拙&#xff0c;导致了很多仍然具有一定价值或者具有非常价值的二手商品的囤积&#xff0c;乃至被当作废弃物处理。现在通过微信小程序的校园二手交易平台&a…

系统集成项目管理工程师2022年下半年广东卷下午案例分析题及答案

本系列文章将会对系统集成项目管理工程师考试中出现的各类案例分析题进行汇总解析&#xff0c;并给出分析过程&#xff0c;帮助考生备考复习。 更多复习内容请在微信搜索小程序 “系统集成项目管理工程师高频考点”。 1、A公司承接了一个信息系统开发项目&#xff0c;任命小安…

【现代机器人学】学习笔记四:一阶运动学与静力学

这节课的内容主要讲速度的正向运动学&#xff08;也就是位置的一阶导数&#xff0c;所以叫一阶运动学&#xff09;和静力学&#xff0c;这也是本书首次出现动力学相关的内容&#xff08;刚体运动那节提到的力旋量算是一个概念的介绍&#xff09;。 个人结合平时的工程项目看&a…

【DeepFuse:无监督用于与极端曝光图像】

DeepFuse: 一种深度无监督的方法&#xff0c;用于与极端曝光图像对进行曝光融合 DeepFuse: A Deep Unsupervised Approach for Exposure Fusion with Extreme Exposure Image Pairs 传统手工进行的MEF&#xff08;多曝光融合&#xff09;&#xff0c;对输入条件变化大的鲁棒性…

算法6.7BFS 算法6.8-6.9最小生成树

一个不知名大学生&#xff0c;江湖人称菜狗 original author: jacky Li Email : 3435673055qq.com Time of completion&#xff1a;2022.12.10 Last edited: 2022.12.11 目录 算法6.7BFS 第1关&#xff1a;算法6.7 BFS 任务描述 相关知识 编程要求 输入输出说明 测试说明…