当前位置: 首页 > article >正文

基于Java + Redis + RocketMQ的库存秒杀系统设计与实现

一、秒杀场景核心挑战

  1. 瞬时高并发:万级QPS访问压力

  2. 库存准确性:避免超卖/少卖

  3. 系统可用性:防止雪崩效应

  4. 数据一致性:缓存与数据库同步

二、技术架构设计

1. 分层架构设计

用户请求
  │
  ▼
接入层(Nginx限流 + 令牌拦截)
  │
  ▼
逻辑层(Redis预扣库存 + RocketMQ异步)
  │
  ▼
数据层(MySQL最终扣减)

2. 核心流程

sequenceDiagram
    participant 用户
    participant 网关
    participant Redis
    participant RocketMQ
    participant MySQL
    
    用户->>网关: 提交秒杀请求
    网关->>Redis: 执行预扣库存(LUA)
    alt 库存不足
        Redis-->>用户: 秒杀失败
    else 库存足够
        Redis->>RocketMQ: 发送异步消息
        RocketMQ-->>网关: 返回受理成功
        网关-->>用户: 提示排队中
        RocketMQ->>MySQL: 消费消息执行真实扣减
    end

三、核心代码实现

1. 环境依赖

<!-- Spring Boot Starter -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- RocketMQ -->
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

2. Redis预扣库存(LUA脚本)

-- seckill.lua
local key = KEYS[1]
local quantity = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key) or '0')

if current >= quantity then
    redis.call('decrby', key, quantity)
    return 1 -- 成功
else
    return 0 -- 失败
end

3. 秒杀接口实现

@RestController
public class SeckillController {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    // 加载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);
    }

    @PostMapping("/seckill")
    public ResponseEntity<String> seckill(@RequestParam Long itemId, 
                                        @RequestParam Integer userId) {
        // 1. 参数校验
        if (itemId == null || userId == null) {
            return ResponseEntity.badRequest().body("参数错误");
        }

        // 2. 执行LUA脚本预扣库存
        List<String> keys = Collections.singletonList("stock:" + itemId);
        Long result = redisTemplate.execute(SECKILL_SCRIPT, keys, "1");
        
        // 3. 处理结果
        if (result == 1) {
            // 发送MQ消息
            rocketMQTemplate.send("seckill_topic", 
                MessageBuilder.withPayload(new SeckillMessage(itemId, userId)).build());
            return ResponseEntity.ok("秒杀请求已受理");
        } else {
            return ResponseEntity.status(429).body("库存不足");
        }
    }
}

4. RocketMQ消费者

@RocketMQMessageListener(
    topic = "seckill_topic",
    consumerGroup = "seckill_consumer_group"
)
@Service
public class SeckillConsumer implements RocketMQListener<SeckillMessage> {

    @Autowired
    private StockService stockService;

    @Override
    public void onMessage(SeckillMessage message) {
        // 数据库真实扣减
        stockService.reduceStock(message.getItemId());
        // 记录订单等后续操作...
    }
}

5. 数据库操作

@Service
public class StockServiceImpl implements StockService {

    @Autowired
    private StockMapper stockMapper;

    @Transactional
    public boolean reduceStock(Long itemId) {
        // 乐观锁更新
        int rows = stockMapper.reduceStockWithVersion(itemId);
        if (rows == 0) {
            // 库存补偿逻辑
            handleStockRollback(itemId);
            return false;
        }
        return true;
    }
}

<!-- MyBatis Mapper -->
<update id="reduceStockWithVersion">
    UPDATE stock 
    SET quantity = quantity - 1,
        version = version + 1
    WHERE item_id = #{itemId} 
    AND quantity > 0
    AND version = #{version}
</update>

四、关键优化设计

1. 库存预热方案

// 活动开始前加载库存到Redis
public void preheatStock(Long itemId, Integer stock) {
    redisTemplate.opsForValue().set("stock:" + itemId, stock.toString());
}

2. 限流策略

// 使用Guava RateLimiter做令牌桶限流
private RateLimiter rateLimiter = RateLimiter.create(1000); // 每秒1000个请求

@PostMapping("/seckill")
public ResponseEntity<String> seckill(...) {
    if (!rateLimiter.tryAcquire()) {
        return ResponseEntity.status(429).body("请求过于频繁");
    }
    // 原有逻辑...
}

3. 降级方案

@Value("${seckill.switch:true}")
private boolean seckillSwitch;

@PostMapping("/seckill")
public ResponseEntity<String> seckill(...) {
    if (!seckillSwitch) {
        return ResponseEntity.status(503).body("活动暂未开始");
    }
    // 原有逻辑...
}

4. 防刷策略

// Redis记录用户访问频率
String userKey = "user_limit:" + userId;
Long count = redisTemplate.opsForValue().increment(userKey, 1);
if (count != null && count > 5) { // 每秒限制5次
    return ResponseEntity.status(429).body("操作过于频繁");
}
redisTemplate.expire(userKey, 1, TimeUnit.SECONDS);

五、监控与告警设计

1. 监控指标

- Redis库存余量
- MQ堆积量
- 接口QPS/TPS
- 数据库连接池使用率

2. 告警规则

- MQ消息堆积超过10000条
- Redis内存使用率>80%
- 接口失败率>1%

六、压测验证方案

  1. JMeter压测脚本配置

    • 5000并发用户持续30秒

    • 添加思考时间300ms

    • 监控TPS/RT/错误率

  2. 验证指标

    - 请求成功率 ≥99.99%
    - 平均响应时间 <500ms
    - 数据库最终一致性100%

七、总结

架构优势

  • 流量分层过滤:拦截80%无效请求

  • 读写分离:Redis承担99%的读压力

  • 异步解耦:MQ保证最终一致性

  • 柔性可用:快速失败+自动降级

扩展方向

  1. 增加分布式锁防止重复下单

  2. 引入Sentinel实现熔断限流

  3. 使用Redis Cluster提升缓存容量

  4. 添加异步订单日志


http://www.kler.cn/a/584762.html

相关文章:

  • Linux网络套接字编程——UDP服务器
  • 江科大51单片机笔记【17】红外遥控(外部中断)
  • 第八节:红黑树(初阶)
  • T2.小牛架炮 - 美团机试真题题解
  • 【vscode-01】vscode不同项目不同语言扩展插件隔离方案
  • Mermaid 子图 + 拖拽缩放:让流程图支持无限细节展示
  • 边缘计算(Edge Computing)
  • 伽马函数相关知识点及其与概率论知识的结合
  • WPF 制作机械手动画
  • 个性化音乐推荐系统
  • 面向联邦学习隐私保护的同态加密库优化算法研究
  • tcpdump 基础参数解析与简单示例
  • 【每日学点HarmonyOS Next知识】页面引用问题、Json三方库、路由表使用、下拉刷新问题、视频播放错误
  • 【QT】-解析打包json
  • 并行计算编程模型的发展方向与RISC-V的机遇
  • BUG修复 | 一次钉钉工作台应用远程调试实战(开发者工具)
  • laravel项目中使用FFMPeg 剪裁视频
  • Pytortch深度学习网络框架库 torch.no_grad方法 核心原理与使用场景
  • 重生之我在学Vue--第11天 Vue 3 高级特性
  • 版本控制泄露源码 .git