redis lua脚本解决高并发下秒杀场景

 更新时间:2023年10月07日 15:05:50   作者:光法V3  
这篇文章主要为大家介绍了redis lua脚本解决高并发下秒杀场景,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

Redis lua脚本解决抢购秒杀场景

秒杀抢购可以说是在分布式环境下⼀个⾮常经典的案例,⾥边有很多痛点:

1.⾼并发: 时间极短、瞬间⽤户量⼤,⼀瞬间的⾼QPS把系统或数据库直接打死,响应失败,导致与这个系统耦合的系统也GG

目前秒杀的实现方案主要有两种:

2.超卖: 你只有⼀百件商品,由于是⾼并发的问题,导致超卖的情况

目前秒杀的实现方案主要有两种:

1.用redis 将抢购信息进行存储。然后再慢慢消费。 同时,服务器给与用户快速响应。

2.用mq实现,比如RabbitMQ,服务器将请求过来的数据先让RabbitMQ存起来,然后再慢慢消费掉。

也可以结合redis与mq的方式,通过redis控制剩余库存,达到快速响应,将满足条件的购买的订单先让RabbitMQ存起来,后续在慢慢消化。

整体流程

1.服务器接收到了大量用户请求过来(1s 2000个请求)。比如传了用户信息,产品信息,和购买数量信息。此时 服务器采用redis 的lua 脚本 去调用redis 中间件。lua 脚本的逻辑是减库存,校验库存是否足够。然后迅速给与服务器反馈(库存是否够,够返回 1 ,不够返回 0)。

2.服务器迅速给与用户的请求反馈。提示抢购成功.或者抢购失败

3.抢购成功,将订单信息放入MQ,其余线程接受到MQ的信息后,将订单信息存入DB中

4.后面客户就可以查询 mysql 的订单信息了。

架构

采用springboot+redis+mysql+myBatis.

数据库

CREATE TABLE `tb_product` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `product_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'id',
   `price` decimal(65,18) NOT NULL DEFAULT '0',
   `available_qty` bigint NOT NULL DEFAULT '0' COMMENT '发行数量',
  `title` varchar(1024) NOT NULL DEFAULT '',
   `end_time` bigint NOT NULL DEFAULT '0',
  `start_time` bigint NOT NULL DEFAULT '0',
  `created` bigint NOT NULL DEFAULT '0',
  `updated` bigint NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

pom依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

lua 脚本

1.减少库存,校验库存是否充足

2.库存数量回滚:

核心业务代码展示

1.加载lua脚本

private final static DefaultRedisScript<Long> deductRedisScript = new DefaultRedisScript();
    private final static DefaultRedisScript<Long> increaseRedisScript = new DefaultRedisScript();   
//加载lua脚本
    @PostConstruct
    void init() {
        //加载削减库存lua脚本
        deductRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/fixedDeductInventory.lua")));
        deductRedisScript.setResultType(Long.class);
        //加载库存回滚lua脚本
        increaseRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis/fixedIncreaseInventory.lua")));
        increaseRedisScript.setResultType(Long.class);
    }

2.添加库存到redis

注意点:在使用redis集群时,lua脚本中存在多个key时,可以通过hash tag这个方法将不同key的值落在同一个槽位上,hash tag 是通过{}这对括号括起来的字符串,如果下列中{fixed:" + data.getProductId() + "} 作为tag,确保同一个产品的信息都在同一个槽位。

@Resource(name = "fixedCacheRedisTemplate")
    private RedisTemplate<String, Long> fixedCacheRedisTemplate;
public void ProductToOngoing(Product data, Long time) {
         //设置数量
            long number = data.getAvailableQty();
            fixedCacheRedisTemplate.opsForHash().putIfAbsent("{fixed:" + data.getProductId() + "}-residue_stock_" + data.getRecordId(),
                    "{fixed:" + data.getProductId() + "}-residueStock" , number);
            String statusKey = "fixed_product_sold_status_"+ data.getRecordId();
            long timeout = data.getEndTime() - data.getStartTime();
              //添加产品出售状态
            fixedCacheRedisTemplate.opsForValue().set(statusKey, 1L, data.getEndTime() - data.getStartTime(), TimeUnit.MILLISECONDS);
    }

3.下单&库存校验

//检查库存
public boolean checkFixedOrderQty(Long userId, Long productId, Long quantity, Long overTime) {
        Boolean pendingOrder = false;
        String userKey = "";
        try {
            //校验是否开始
            String statusKey = "fixed_product_sold_status_" + productId;
            Long fixedStartStatus = fixedCacheRedisTemplate.opsForValue().get(statusKey);
            if (fixedStartStatus == null || fixedStartStatus != 1L) {
                //报错返回,商品未开售
                throw new WebException(ResultCode.SALE_HAS_NOT_START);
            }
              //检查库存数量
            Long number = deductInventory(productId, quantity);
            if (number != 1L) {
                log.warn("availbale num is null:{} {}", productId, number);
                throw new WebException(ResultCode.AVAILABLE_AMOUNT_INSUFFICIENT);
            }
            return true;
        } catch (Exception e) {
            log.warn("checkFixedOrderQty error:{}", e.getMessage(), e);
            throw e;
        }
    }
//下单
 public void createOrder(Long userId, Long productId, BigDecimal price, Long quantity){
     boolean check = checkFixedOrderQty(userId, productId, quantity);
        try {
            if (check) {
                //添加MQ等待下单,后续收到推送的线程保存靠DB中
                CreateCoinOrderData data = new CreateCoinOrderData();
                data.setUserId(userId);
                data.setProductId(productId);
                data.setPrice(price);
                data.setQuantity(quantity);
                rabbitmqProducer.sendMessage(1, JSONObject.toJSONString(data));
            }
        } catch (Exception e) {
            //发生异常,库存需要回滚
            increaseInventory(recordId, quantity, 1L);
            throw e;
        }
 }
    //库存回填
    public Long increaseInventory(Long productId, Long num) {
        try {
            // 构建keys信息,代表hash值中所需要的key信息
            List<String> keys = Arrays.asList("{fixed:" + productId + "}-residue_stock_"+ recordId, "{fixed:" + productId + "}-residueStock");
            // 执行脚本
            Object result = fixedCacheRedisTemplate.execute(increaseRedisScript, keys, num);
            log.info("increaseInventory productId :{} num:{}  result:{}", productId, num, result);
            return (Long) result;
        } catch (Exception e) {
            log.warn("increaseInventory error productId:{}  num:{}", productId, num);
        }
        return 0L;
    }

以上就是redis lua脚本解决高并发下秒杀场景的详细内容,更多关于redis lua高并发秒杀的资料请关注脚本之家其它相关文章!

相关文章

  • Redis之BigKey与HotKey问题详解

    Redis之BigKey与HotKey问题详解

    在Redis的实际使用过程中,BigKey和 HotKey是两个常见且可能导致严重性能问题的问题,它们可能导致 Redis 实例响应变慢、内存使用不均、甚至服务不可用,下面就来介绍一下这两个的问题
    2026-02-02
  • 在Redis中如何保存时间序列数据详解

    在Redis中如何保存时间序列数据详解

    与发生时间相关的一组数据,就是时间序列数据,这些数据的特点是没有严格的关系模型,记录的信息可以表示成键和值的关系,这篇文章主要给大家介绍了关于在Redis中如何保存时间序列数据的相关资料,需要的朋友可以参考下
    2021-10-10
  • redis三种高可用方式部署的实现

    redis三种高可用方式部署的实现

    这篇文章主要介绍了redis三种高可用方式部署的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-05-05
  • Redis主从复制问题和扩容问题的解决思路

    Redis主从复制问题和扩容问题的解决思路

    这篇文章主要介绍了Redis主从复制问题和扩容问题的解决思路,其中扩容问题的解决思路来自Redis作者,需要的朋友可以参考下
    2014-06-06
  • redis的bigkey扫描脚本深入介绍

    redis的bigkey扫描脚本深入介绍

    这篇文章主要给大家介绍了关于redis的bigkey扫描脚本的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-07-07
  • redis远程连接不上的解决办法

    redis远程连接不上的解决办法

    本文主要介绍了redis远程连接不上的解决办法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • ubuntu 16.04安装redis的两种方式教程详解(apt和编译方式)

    ubuntu 16.04安装redis的两种方式教程详解(apt和编译方式)

    这篇文章主要介绍了ubuntu 16.04安装redis的两种方式教程详解(apt和编译方式),需要的朋友可以参考下
    2018-03-03
  • Redis从基础到Stream消息队列实战指南

    Redis从基础到Stream消息队列实战指南

    Redis是一个高性能内存数据库,支持字符串、哈希、列表等数据结构,5.0版本新增Stream类型,提供消息队列功能,支持消费者组和消息确认,本文介绍Redis从基础到Stream消息队列实战指南,感兴趣的朋友跟随小编一起看看吧
    2026-03-03
  • Redis如何实现计数统计

    Redis如何实现计数统计

    这篇文章主要介绍了Redis如何实现计数统计方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-04-04
  • Redis缓存雪崩的物种解决方案

    Redis缓存雪崩的物种解决方案

    在高并发系统中,Redis作为核心缓存组件,通常扮演着重要的"守门员"角色,当大量缓存同时失效时,会导致请求如洪水般直接涌向数据库,造成数据库瞬间压力剧增甚至宕机,这种现象被形象地称为"缓存雪崩",本文给大家介绍了Redis缓存雪崩的5种应对措施,需要的朋友可以参考下
    2025-04-04

最新评论