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的分布式幂等框架设计与实现 - 摸鱼不慌

目录
相关文章
|
1月前
|
存储 监控 API
基于 RAG + LangChain 搭建企业级私有知识库问答系统(2026 实战版)
本文是作者基于多个企业RAG知识库落地经验的实战总结,提供完整可运行代码与十年避坑指南。涵盖文档解析、混合检索、向量存储、DeepSeek接入、结果重排、拒答机制及效果评估,助你构建本地可运行、生产可扩展的企业级私有知识库系统。(239字)
412 1
|
1月前
|
人工智能 JSON NoSQL
从零构建 AI Agent:基于 LangGraph 的多工具智能体实战(含完整代码)
本文详解如何用LangGraph从零构建生产级AI Agent:支持自主规划、多工具调用(天气/搜索/计算/笔记)、失败重试与Redis会话记忆。代码开箱即用,涵盖架构设计、状态图实现及流式输出等核心能力,助企业突破RAG局限,落地真实业务场景。
263 0
|
22天前
|
监控 Java 数据库连接
SpringBoot3全栈开发实战:从入门到精通的完整指南
本文是SpringBoot3的精要总结,涵盖自动配置、起步依赖、嵌入式服务器等核心特性,详解项目搭建、YAML配置、Web开发、MyBatis整合、热部署、定时任务、Actuator监控及原生镜像等高级功能,并附思维导图助快速复习。
141 0
SpringBoot3全栈开发实战:从入门到精通的完整指南
|
1月前
|
缓存 运维 架构师
基于 RAG + LangChain + FastAPI 搭建生产级私有知识库问答系统(完整可运行)
本文以十年架构师视角,详解如何用RAG解决大模型落地三大痛点:知识滞后、私有文档不可读、幻觉。提供一套生产级、可运行、可扩展的FastAPI+LangChain+Chroma后端方案,含文档解析、智能分块、混合检索、重排、缓存与评估,代码经实测,开箱即用。(239字)
264 4
|
30天前
|
前端开发 Java 数据库连接
Spring Boot 详细简介!
Spring Boot 是什么?能干啥?
248 0
Spring Boot 详细简介!
|
1月前
|
监控 NoSQL Java
生产级实战:基于Spring Boot + Redis的分布式延迟队列设计与实现
本文介绍基于Redis的生产级分布式延迟队列方案:摒弃ZSet简单轮询(避免CPU空转与惊群效应),采用分级时间轮(近实时秒级+远时分钟级)、Sorted Set+List+Pub/Sub组合及Lua原子脚本,结合优雅停机、幂等处理与监控告警,显著降低IO压力,保障高并发下的可靠性与稳定性。
130 2
|
1月前
|
人工智能 编解码 安全
英特尔中国加速适配国产AI模型MiniMax H3布局本地化部署
英特尔锐炫Pro B70完成对国产开源视频模型MiniMax H3的Day 0适配,支持15秒2K+立体声视频生成。依托32GB显存、367 TOPS算力及8卡混合并行方案,实现开箱即用、低成本本地部署,加速AI视频商业化落地。(239字)
235 1
|
6月前
|
人工智能 Linux API
【最全】零基础玩转OpenClaw:阿里云/本地部署步骤+Skill清单+免费API配置一站式教程
OpenClaw(原Clawdbot)并非简单的AI聊天机器人,而是一套具备任务自动化、跨平台协作能力的AI操作系统,Skill作为其核心扩展能力,能让AI真正实现“动手干活”。2026年的OpenClaw生态已拥有3000+Skill,覆盖30余个应用领域,无论是个人提升办公效率,还是团队实现轻量协作,都能通过合理配置满足需求。本文将从零基础部署入手,完整讲解阿里云、MacOS、Linux、Windows11的OpenClaw部署流程,搭配阿里云百炼API配置方法、必装Skill清单及常见问题解答,让新手也能快速上手,真正让AI落地工作场景。
848 3
|
1月前
|
消息中间件 NoSQL 关系型数据库
商城系统秒杀场景下的库存防超卖架构设计与实践
本文详述电商高并发场景下库存超卖问题的完整解决方案:从TOCTOU根因分析,到数据库行锁、Redis Lua预扣、RocketMQ异步落库+幂等保障的三阶段演进,最终基于阿里云SLB/Redis/RDS/RocketMQ/OSS构建稳定架构。5000 QPS压测零超卖,TPS达4600+,数据库CPU降至35%。
|
1月前
|
缓存 API 异构计算
基于 LangChain + 通义千问 + Chroma 的本地知识库 RAG 问答系统:从零到生产的完整实战
本文提供一套开箱即用的RAG企业级知识库系统,基于LangChain 0.x + Chroma + 通义千问qwen-turbo,覆盖文档加载、切分、向量化、检索、生成全链路,并集成查询改写、LLM兜底、文件溯源、流式输出等工程能力,代码模块清晰,可直接落地生产。(239字)
189 0

热门文章

最新文章