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

基于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. 添加异步订单日志

相关文章:

  • Langchain应用-rag优化
  • 微信小程序从右向左无限滚动组件封装(类似公告)
  • 命令设计模式
  • 【计算机网络】第八版和第七版的主要区别,附PDF
  • 【python】不规则字符串模糊匹配(fuzzywuzzy)
  • AI自动化、资本短视、三输与破局
  • pytest基础知识
  • golang的Map
  • RCE-Labs超详细WP-Level13Level14(PHP下的0/1构造RCE命令简单的字数限制RCE)
  • RabbitMQ 的工作模式
  • 设计模式之适配器模式:原理、实现与应用
  • ConcurrentModificationException:检测到并发修改完美解决方法
  • PDF文件里到底有什么?
  • Centos离线安装gcc
  • 一个c++对象池的示例
  • 『Rust』Rust运行环境搭建
  • 【08】单片机编程核心技巧:变量命名规范
  • 鸿蒙应用开发—ZDbUtil高效使用数据库
  • 线性回归中的最小二乘法:直接法与梯度下降的比较
  • Qt不同窗口类的控件信号和槽绑定
  • 企业网站需要哪些功能/最新的全国疫情
  • 手机网站开发免费视频教程/江苏网站建站系统哪家好
  • 简单的网页设计作品欣赏/seo模拟点击工具
  • 怎么做一个免费的网站/深圳市seo网络推广哪家好
  • 国外做的好的医疗网站设计/网站维护
  • 医疗网站建设怎么样/图片外链