Spring Boot + Redis + RabbitMQ 高并发秒杀系统实战

简介: 本文基于生产级秒杀系统架构,从零搭建支撑万级QPS的高并发系统,融合Redis预扣库存(防超卖)、RabbitMQ异步削峰、MySQL最终落库及双重校验机制,并集成令牌桶限流与用户维度防刷,附完整可运行代码。

 💡 本文基于生产级秒杀系统架构,从零到一搭建一个支撑万级 QPS 的秒杀系统,涵盖 Redis 预扣库存、RabbitMQ 异步下单、分布式锁等核心技术,附完整代码。

一、项目背景与架构设计

秒杀系统的核心挑战是瞬时高并发超卖问题。传统数据库直击方案在万级 QPS 下会直接被打垮。本文采用以下架构:

用户请求 → Nginx → Spring Boot → Redis(预扣库存) → RabbitMQ(削峰) → MySQL(持久化)


核心设计思路

  1. Redis 预扣库存:利用 Redis 单线程特性保证原子性,避免超卖
  2. RabbitMQ 削峰:将同步下单转为异步,保护后端数据库
  3. 双重校验:Redis 预扣 + MySQL 事务确认,保证数据一致性

二、项目环境

组件

版本

Spring Boot

2.7.x

Redis

6.2

RabbitMQ

3.9

MySQL

8.0

三、核心代码实现

1. Maven 依赖

<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-amqp</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency></dependencies>

image.gif

2. Redis 配置类

@Configurationpublic class RedisConfig {    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);        
        // 使用 Jackson2JsonRedisSerializer 序列化 value
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        
        template.setValueSerializer(serializer);
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();        return template;
    }
}

image.gif

3. 秒杀核心 Service(Redis 预扣库存)

@Service@Slf4jpublic class SeckillService {    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;    
    @Autowired
    private RabbitTemplate rabbitTemplate;    
    @Autowired
    private OrderMapper orderMapper;    
    // 商品库存 Key 前缀
    private static final String STOCK_KEY_PREFIX = "seckill:stock:";    // 已秒杀用户 Set Key 前缀
    private static final String USER_SET_PREFIX = "seckill:users:";    
    /**
     * 秒杀核心方法
     */
    public Result seckill(Long userId, Long goodsId) {        String stockKey = STOCK_KEY_PREFIX + goodsId;        String userSetKey = USER_SET_PREFIX + goodsId;        
        // 1. 校验用户是否已秒杀过(防止重复购买)
        Boolean isMember = redisTemplate.opsForSet().isMember(userSetKey, userId);        if (Boolean.TRUE.equals(isMember)) {            return Result.error("您已参与过该秒杀活动");
        }        
        // 2. Redis 预扣库存(原子操作)
        Long remaining = redisTemplate.opsForValue().decrement(stockKey);        if (remaining == null || remaining < 0) {            // 库存不足,回补
            redisTemplate.opsForValue().increment(stockKey);            return Result.error("秒杀失败,库存不足");
        }        
        // 3. 记录用户已秒杀
        redisTemplate.opsForSet().add(userSetKey, userId);        
        // 4. 发送消息到 RabbitMQ 异步下单
        SeckillMessage message = new SeckillMessage(userId, goodsId);
        rabbitTemplate.convertAndSend(            "seckill.exchange", 
            "seckill.route", 
            message
        );
        
        log.info("用户 {} 秒杀商品 {} 进入队列", userId, goodsId);        return Result.success("秒杀成功,订单处理中");
    }
}

image.gif

4. RabbitMQ 配置

@Configurationpublic class RabbitMQConfig {    
    // 交换机
    public static final String SECKILL_EXCHANGE = "seckill.exchange";    // 队列
    public static final String SECKILL_QUEUE = "seckill.queue";    // 路由键
    public static final String SECKILL_ROUTING_KEY = "seckill.route";    
    @Bean
    public DirectExchange seckillExchange() {        return new DirectExchange(SECKILL_EXCHANGE);
    }    
    @Bean
    public Queue seckillQueue() {        return QueueBuilder.durable(SECKILL_QUEUE)
            .withArgument("x-message-ttl", 60000)  // 消息60秒过期
            .withArgument("x-max-length", 100000)  // 最大队列长度
            .build();
    }    
    @Bean
    public Binding binding() {        return BindingBuilder.bind(seckillQueue())
            .to(seckillExchange())
            .with(SECKILL_ROUTING_KEY);
    }
}

image.gif

5. RabbitMQ 消费者(MySQL 落库)

@Component@Slf4jpublic class SeckillConsumer {    
    @Autowired
    private OrderMapper orderMapper;    
    @Autowired
    private GoodsMapper goodsMapper;    
    @RabbitListener(queues = "seckill.queue")
    public void handleSeckillMessage(SeckillMessage message, Channel channel, 
                                     @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws IOException {        try {            Long userId = message.getUserId();            Long goodsId = message.getGoodsId();            
            // 1. 数据库库存校验(兜底)
            Goods goods = goodsMapper.selectById(goodsId);            if (goods == null || goods.getStock() <= 0) {
                log.warn("商品 {} 库存不足", goodsId);
                channel.basicAck(deliveryTag, false);                return;
            }            
            // 2. 创建订单
            Order order = new Order();
            order.setUserId(userId);
            order.setGoodsId(goodsId);
            order.setStatus(0); // 待支付
            order.setCreateTime(LocalDateTime.now());
            orderMapper.insert(order);            
            // 3. 扣减数据库库存(乐观锁防止超卖)
            int updated = goodsMapper.decreaseStock(goodsId);            if (updated == 0) {
                log.warn("商品 {} 数据库扣库存失败", goodsId);
            }
            
            log.info("用户 {} 秒杀商品 {} 下单成功,订单ID: {}", 
                userId, goodsId, order.getId());            
            // 手动 ACK
            channel.basicAck(deliveryTag, false);
            
        } catch (Exception e) {
            log.error("处理秒杀消息失败", e);            // 重试3次后进入死信队列
            channel.basicNack(deliveryTag, false, true);
        }
    }
}

image.gif

6. 商品 Mapper(乐观锁扣库存)

@Mapperpublic interface GoodsMapper extends BaseMapper<Goods> {    
    @Update("UPDATE goods SET stock = stock - 1, " +
            "version = version + 1 WHERE id = #{goodsId} " +
            "AND stock > 0 AND version = #{version}")
    int decreaseStockWithVersion(@Param("goodsId") Long goodsId, 
                                 @Param("version") Integer version);    
    @Update("UPDATE goods SET stock = stock - 1 " +
            "WHERE id = #{goodsId} AND stock > 0")
    int decreaseStock(@Param("goodsId") Long goodsId);
}

image.gif

7. Controller 层

@RestController@RequestMapping("/seckill")@Slf4jpublic class SeckillController {    
    @Autowired
    private SeckillService seckillService;    
    @PostMapping("/{goodsId}")
    public Result doSeckill(@PathVariable Long goodsId, 
                            @RequestHeader("X-User-Id") Long userId) {        return seckillService.seckill(userId, goodsId);
    }    
    /**
     * 初始化库存到 Redis
     */
    @PostMapping("/init/{goodsId}")
    public Result initStock(@PathVariable Long goodsId) {        // 从数据库加载库存到 Redis
        // ...
        return Result.success("库存初始化完成");
    }
}

image.gif

四、关键优化点

1. 防止超卖的双重保障

// 第一层:Redis 原子递减Long remaining = redisTemplate.opsForValue().decrement(stockKey);// 第二层:MySQL 乐观锁int updated = goodsMapper.decreaseStockWithVersion(goodsId, version);

image.gif

2. 接口限流(基于 Redis 令牌桶)

public boolean tryAcquire(String key, int qps) {    String luaScript = 
        "local key = KEYS[1] " +        "local limit = tonumber(ARGV[1]) " +        "local current = tonumber(redis.call('get', key) or '0') " +        "if current + 1 > limit then " +        "   return 0 " +        "else " +        "   redis.call('incrby', key, 1) " +        "   redis.call('expire', key, 1) " +        "   return 1 " +        "end";    
    Long result = redisTemplate.execute(        new DefaultRedisScript<>(luaScript, Long.class),
        Collections.singletonList(key),
        String.valueOf(qps)
    );    return result != null && result == 1L;
}

image.gif

3. 防刷策略

// 用户维度限流:同一用户5秒内只能请求1次String userLimitKey = "seckill:limit:" + userId;if (redisTemplate.opsForValue().setIfAbsent(userLimitKey, "1", 5, TimeUnit.SECONDS)) {    // 允许请求} else {    return Result.error("操作过于频繁,请稍后再试");
}

image.gif

五、性能压测数据

使用 JMeter 进行压测,模拟 10000 并发:

方案

QPS

平均响应时间

错误率

直击数据库

800

1200ms

35%

Redis 预扣+MQ

12000

15ms

0%

六、生产环境踩坑总结

⚠️ 踩坑1:Redis 与数据库库存不一致

解决方案:定时任务对账,以数据库为准修复 Redis 库存

⚠️ 踩坑2:RabbitMQ 消息堆积

解决方案:增加消费者实例数 + 设置队列 TTL 和死信队列

⚠️ 踩坑3:用户重复秒杀

解决方案:Redis Set 记录已参与用户 + 数据库唯一索引兜底

七、完整项目结构

src/main/java/com/example/seckill/
├── config/
│   ├── RedisConfig.java
│   └── RabbitMQConfig.java
├── controller/
│   └── SeckillController.java
├── service/
│   └── SeckillService.java
├── mq/
│   └── SeckillConsumer.java
├── mapper/
│   ├── GoodsMapper.java
│   └── OrderMapper.java
└── entity/
    ├── Goods.java
    ├── Order.java
    └── SeckillMessage.java

image.gif

本文由 摸鱼不慌 发布,转载请注明出处。

文章链接:生产级实战:基于Redis Lua的分布式幂等框架设计与实现 - 摸鱼不慌

目录
相关文章
|
6天前
|
存储 弹性计算 缓存
阿里云服务器租赁费用:新版租赁收费标准及活动报价参考
本文更新了2026年阿里云全系列云服务器租赁活动报价,所有特惠资源均可前往阿里云活动中心选购,整体覆盖从个人入门到企业级高性能场景的全梯度需求。其中轻量应用服务器主打极致性价比,2核2G峰值200M带宽配置每日10点、15点限时抢购价仅38元/年,2核4G配置379元/年起;高性价比的经济型e实例、通用算力型u2i实例覆盖2核4G至4核32G全档位,适配开发测试与中小型企业业务;搭载英特尔至强6处理器的第九代c9i企业级实例算力较上代提升20%,支撑高并发生产环境,不同实例规格价差清晰,用户可根据自身业务负载与预算灵活选型。
1616 116
|
7天前
|
人工智能 程序员 API
Codex 接入 DeepSeek-V4-Flash:还能补上识图,提供两套方案
Codex 接入 DeepSeek-V4-Flash 怎么配?本文覆盖 CLI 与桌面端,再用 qwen3-vl-flash 补识图,两套方案可直接照做
1096 5
|
13天前
|
云安全 人工智能 运维
阿里云联动百位企业安全专家,共识Agent防御最佳实践
当Agent成为新员工,你的安全边界在哪里?
1954 9
阿里云联动百位企业安全专家,共识Agent防御最佳实践
|
7天前
|
编解码 人工智能 安全
2核4G/4核8G/8核16G阿里云服务器如何选择实例?经济型e、通用算力型u2i与计算型c9i选哪个?
本文介绍了阿里云2核4G、4核8G、8核16G三档主流配置下经济型e、通用算力型u2i和计算型c9i三种实例的最新活动价格与适用场景。同配置下三者价差显著,以2核4G为例,经济型e低至599.93元/年,计算型c9i则高达1742.08元/年。文章详细解析了各实例的性能定位:经济型e适合轻负载入门场景,u2i兼顾稳定算力与性价比,c9i凭借第9代至强处理器与芯片级安全能力支撑高性能业务。同时提示用户可叠加满减优惠券享受折上折,建议根据业务负载与预算综合决策。
538 112
|
19天前
|
人工智能 前端开发 Linux
Codex 桌面版安装 + CC Switch 接入第三方 API 完整教程(2026 最新)
2026最新教程:手把手教你安装Codex桌面版,通过CC Switch v3.17.0一键接入Fenno等国产API(兼容OpenAI Responses格式),跳过账号登录,完整启用代码审查、多步任务与上下文感知功能。零基础友好,全程图文实操。(239字)
2745 4
|
11天前
|
存储 人工智能 关系型数据库
阿里云AI产品与云产品最新组合套餐:Token Plan、AI coding及云服务器和建站等组合优惠价
阿里云推出全新“算力+模型+应用”一站式云与AI组合套餐活动,覆盖从个人开发者到中大型企业的全场景需求。核心亮点为分三档定价的Token Plan订阅服务,支持Qwen3.8-Max-Preview大模型调用,错峰时段最低可享0.2折优惠。活动同步推出AI Coding、智能体部署、云电脑托管、0代码建站等十余类场景化组合,搭配99元/年的普惠云服务器、88元/年的入门数据库等经典特惠产品,还为企业提供1V1定制化AI转型方案,大幅降低了不同用户群体拥抱AI的技术门槛与采购成本。
730 111
|
21天前
|
人工智能 JSON 安全
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
阿里云AI安全产品联动防御Fastjson攻击
2652 13
Fastjson远程代码执行漏洞,阿里云AI安全为您保驾护航
|
7天前
|
人工智能 JSON Shell
2026AI漫剧本地全开源方案(附各个软件模型链接),8G显卡也能流畅运行
这是一套完全本地化部署的AI漫剧生成技术链路:涵盖LLM剧本分镜生成、FLUX文生图(IP-Adapter人脸锁定)、StoryDiffusion时序连贯控制、LTX-2.3唇形同步视频生成,及ComfyUI全流程调度。零云端费用,仅耗硬件算力,单集2–4小时可产出竖屏短视频,适配抖音/B站分发。
|
5天前
|
人工智能 API 开发工具
2026 零基础本地 AI 漫剧完整实操教程(8G 笔记本显卡可用|附可直接复制命令与代码)
本方案提供完全离线、本地运行的漫剧全自动制作流程:RTX3060/4050 8G显卡即可驱动,涵盖Qwen写分镜→ComfyUI统一角色绘图→LTX2.3图生微动画→Qwen3-TTS本地配音→FFmpeg自动合成,全程无水印、免API、不限次。专为低显存优化,解决变脸、闪烁、爆内存三大痛点。(239字)