
1. 项目背景与核心需求在电商系统中秒杀活动是最具挑战性的场景之一。面对瞬时高并发流量传统数据库直接处理订单的方式往往会导致系统崩溃。我在实际项目中曾遇到过这样一个案例某次促销活动上线后数据库连接池瞬间被占满整个系统陷入瘫痪。这促使我开始探索基于Redis的异步秒杀解决方案。Redis Stream作为Redis 5.0引入的新数据结构完美契合了消息队列的需求。与常见的List、Pub/Sub相比Stream支持消息持久化不会像Pub/Sub那样丢失消息消费者组模式实现负载均衡消息回溯处理失败后可重新消费阻塞读取节省CPU资源2. 技术方案设计2.1 整体架构设计我们的异步秒杀系统采用三层处理架构前端层接收用户请求通过Lua脚本完成资格校验消息层使用Redis Stream作为消息缓冲处理层独立线程消费消息完成订单创建用户请求 → Lua校验 → Stream队列 → 异步处理 → 数据库这种设计将耗时最长的数据库操作异步化前端只需等待Lua脚本执行通常1-2ms极大提升了系统吞吐量。2.2 关键组件选型组件选型理由消息队列Redis Stream无需额外中间件利用现有Redis基础设施分布式锁Redisson比原生Redis锁更完善支持自动续期线程池SingleThreadExecutor顺序处理避免并发问题与Redis单线程特性匹配3. 核心实现细节3.1 Lua脚本优化原始脚本增加了Stream操作逻辑关键改进点-- 原有库存校验逻辑... -- 新增消息发送 redis.call(xadd,stream.orders,*,userId,userId,voucherId,voucherId,id,orderId)注意这里使用*让Redis自动生成消息ID保证消息顺序性。如果需要精确控制时序可以传入自定义时间戳。3.2 消费者组实现在项目启动时初始化消费者线程PostConstruct private void init(){ SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler()); }消费者核心逻辑包含两个关键部分正常消息处理ListMapRecordString, Object, Object list stringRedisTemplate.opsForStream().read( Consumer.from(g1, c1), StreamReadOptions.empty().count(1).block(Duration.ofSeconds(2)), StreamOffset.create(queueName, ReadOffset.lastConsumed()) );Pending List处理处理异常情况ListMapRecordString, Object, Object list stringRedisTemplate.opsForStream().read( Consumer.from(g1, c1), StreamReadOptions.empty().count(1), StreamOffset.create(queueName, ReadOffset.from(0)) );3.3 异常处理机制我们实现了完善的错误处理流程消息消费失败时进入pending-list独立线程定期检查pending-list设置指数退避重试策略代码中的Thread.sleepprivate void handlePendingList() { while(true){ try { // ...处理逻辑 } catch (Exception e) { log.error(处理pending-list订单异常,e); try { Thread.sleep(20); // 简单退避策略 } catch (InterruptedException ex) { throw new RuntimeException(ex); } } } }4. 性能优化实践4.1 基准测试对比在4核8G服务器上测试结果方案QPS平均响应时间直接数据库1200850msRedis队列850012ms4.2 关键配置参数在application.yml中建议配置spring: redis: timeout: 3000ms # 适当增加Stream读取超时 lettuce: pool: max-active: 200 # 根据并发量调整4.3 监控指标建议监控以下关键指标stream.orders队列长度pending-list积压数量消费者处理耗时通过日志中的时间戳计算5. 常见问题与解决方案5.1 消息丢失问题现象服务器重启后部分订单未处理解决方案启用AOF持久化消费者启动时检查pending-list添加重试计数器避免无限重试5.2 顺序消费问题现象优惠券库存足够但提示库存不足原因消息消费顺序与产生顺序不一致解决方案// 创建订单前再次校验库存 boolean success seckillVoucherService.update() .setSql(stock stock - 1) .eq(voucher_id, voucherOrder.getVoucherId()) .gt(stock,0) // 乐观锁 .update();5.3 内存泄漏风险现象Stream队列持续增长解决方案定期清理已确认消息// 保留最近1000条消息 stringRedisTemplate.opsForStream().trim(queueName, 1000);设置消息TTLRedis 6.2支持6. 生产环境部署建议经过多个线上项目验证推荐以下部署方案Redis配置使用独立实例专门处理Stream开启RDBAOF混合持久化内存配置为预估消息量的2倍消费者部署采用多实例部署相同consumer group实现负载均衡每个实例配置相同的线程数建议2-4个灾备方案定期备份Stream消息使用XREAD全量读取准备降级方案如直接写入数据库队列在实际项目中这套方案成功支撑了某电商平台双11期间峰值QPS 12万的秒杀活动系统稳定性达到99.99%。关键点在于前置校验要足够轻量消息格式设计简洁消费者处理要幂等对于想要进一步优化的开发者可以考虑引入本地缓存减少Redis访问使用Cluster模式提高吞吐量添加可视化监控界面