
在构建高并发秒杀系统时确保系统在高流量冲击下仍能保持高性能、高可用和数据一致性是核心目标。经过对业界主流方案的梳理可以提炼出三大核心技术支柱原子性库存扣减、分布式锁防超卖、以及异步消息队列解耦。下面将结合具体技术实现和实战代码详细解析这三大技术。1. 原子性库存扣减Redis Lua脚本秒杀的核心挑战之一是库存的精确扣减。在超高并发下简单的“查询-判断-更新”数据库操作会产生严重的超卖问题。使用Redis配合Lua脚本是确保库存扣减原子性的黄金标准。原理Lua脚本在Redis中执行时是原子的这意味着脚本执行期间不会被其他命令插入从而避免了并发竞争。优势将多个操作如检查库存、扣减库存、记录用户购买资格打包成一个原子操作性能极高微秒级且逻辑清晰。实战代码示例-- 扣减库存的Lua脚本 -- KEYS[1]: 商品库存键如 seckill:stock:1001 -- ARGV[1]: 要扣减的数量通常为1 -- 返回值: 剩余库存若为-1表示库存不足-2表示重复扣减可选逻辑 local stockKey KEYS[1] local change tonumber(ARGV[1]) -- 获取当前库存 local currentStock tonumber(redis.call(GET, stockKey)) if currentStock nil then -- 库存键不存在可能是初始化问题 return -3 end if currentStock change then -- 库存不足 return -1 end -- 扣减库存 local newStock redis.call(DECRBY, stockKey, change) return newStock// Java中调用上述Lua脚本 Component public class SeckillService { Autowired private StringRedisTemplate redisTemplate; private static final DefaultRedisScriptLong SECKILL_SCRIPT; static { SECKILL_SCRIPT new DefaultRedisScript(); SECKILL_SCRIPT.setLocation(new ClassPathResource(scripts/seckill.lua)); SECKILL_SCRIPT.setResultType(Long.class); } public Long handleSeckill(Long productId) { String stockKey seckill:stock: productId; // 原子执行Lua脚本扣减库存 Long result redisTemplate.execute( SECKILL_SCRIPT, Collections.singletonList(stockKey), 1 // 扣减数量 ); if (result ! null result 0) { // 扣减成功进入后续订单流程 return result; // 返回剩余库存 } else if (result -1) { // 库存不足 throw new RuntimeException(商品已售罄); } // 其他错误处理 throw new RuntimeException(秒杀失败); } }2. 分布式锁防超卖Redisson可重入锁即便库存扣减是原子的仍需防止同一用户在极端时间内重复提交或确保如“一人一单”等业务规则的互斥执行。分布式锁是解决分布式环境下互斥访问的关键。选型相比自己基于SETNX实现更推荐使用Redisson客户端它提供了成熟的、可重入的分布式锁实现并解决了锁的自动续期和释放问题。场景用于在创建订单等关键链路上进行串行化控制。实战代码示例// 使用Redisson实现分布式锁确保“一人一单” Service public class OrderService { Autowired private RedissonClient redissonClient; public String createOrder(Long userId, Long productId) { String lockKey seckill:order:lock: productId : userId; RLock lock redissonClient.getLock(lockKey); try { // 尝试加锁最多等待100毫秒锁持有时间10秒 boolean isLocked lock.tryLock(100, 10000, TimeUnit.MILLISECONDS); if (!isLocked) { throw new RuntimeException(系统繁忙请稍后再试); } // 锁内执行核心业务检查是否已下单、创建订单等 // ... 业务逻辑例如查询数据库判断用户是否已有订单 return doCreateOrder(userId, productId); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(锁定异常, e); } finally { // 无论如何最终必须释放锁 if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } private String doCreateOrder(Long userId, Long productId) { // 实际创建订单的数据库操作 // ... 省略数据库插入等逻辑 return 订单创建成功订单号 generateOrderNo(); } }3. 异步消息队列解耦Redis Stream / RabbitMQ秒杀请求峰值极高但创建订单、扣减数据库库存、发送通知等操作是耗时的I/O操作。异步消息队列能将瞬时高并发请求转换为平稳的异步任务流实现流量削峰和系统解耦。两种主流选择Redis Stream轻量级无需额外中间件适合数据量不大、对消息持久化要求不是极端高的场景。RabbitMQ功能强大的专业消息队列支持多种协议、高可靠性和复杂的路由策略。技术对比与选择特性Redis StreamRabbitMQ部署复杂度低与Redis一体中需独立部署功能丰富度基础消息队列功能丰富多种交换机、路由、ACK机制可靠性依赖Redis持久化非常高持久化、确认、事务适用场景轻量级解耦、实时流处理复杂业务解耦、高可靠异步任务实战代码示例 (Redis Stream)// 生产者秒杀成功后将订单任务放入Stream Service public class SeckillProducerService { Autowired private StringRedisTemplate redisTemplate; public void produceOrderTask(SeckillMessage message) { String streamKey stream:seckill:orders; MapString, String messageBody new HashMap(); messageBody.put(userId, message.getUserId().toString()); messageBody.put(productId, message.getProductId().toString()); messageBody.put(seckillId, message.getSeckillId().toString()); // 将消息添加到Stream RecordId recordId redisTemplate.opsForStream().add(streamKey, messageBody); System.out.println(生产消息成功ID: recordId); } } // 消费者从Stream消费并处理订单 Component public class SeckillConsumerService { Autowired private StringRedisTemplate redisTemplate; Autowired private OrderService orderService; PostConstruct public void consumeOrderTask() { String streamKey stream:seckill:orders; String consumerGroup order-group; String consumerName consumer-1; // 创建消费者组如果不存在 try { redisTemplate.opsForStream().createGroup(streamKey, consumerGroup); } catch (Exception e) { // 组可能已存在 } while (true) { // 从消费者组读取待处理消息 ListMapRecordString, Object, Object records redisTemplate.opsForStream().read( Consumer.from(consumerGroup, consumerName), StreamReadOptions.empty().count(10).block(Duration.ofSeconds(2)), StreamOffset.create(streamKey, ReadOffset.lastConsumed()) ); if (records ! null !records.isEmpty()) { for (MapRecordString, Object, Object record : records) { MapObject, Object value record.getValue(); // 处理订单业务 Long userId Long.valueOf((String)value.get(userId)); Long productId Long.valueOf((String)value.get(productId)); try { orderService.asyncCreateOrder(userId, productId); // 处理成功确认消息 (ACK) redisTemplate.opsForStream().acknowledge(streamKey, consumerGroup, record.getId()); } catch (Exception e) { // 处理失败可放入死信队列或记录日志 System.err.println(处理订单失败: record.getId()); } } } } } }实战代码示例 (RabbitMQ)// 使用RabbitMQ进行异步订单处理 Component public class RabbitMQOrderProducer { Autowired private RabbitTemplate rabbitTemplate; public void sendOrderMessage(SeckillMessage message) { // 发送消息到秒杀订单队列 rabbitTemplate.convertAndSend(seckill.order.exchange, seckill.order.routing.key, message); System.out.println( [x] Sent order message for user: message.getUserId()); } } Service public class RabbitMQOrderConsumer { RabbitListener(queues seckill.order.queue) public void receiveOrderMessage(SeckillMessage message) { System.out.println( [x] Received order message for user: message.getUserId()); // 异步处理订单创建 // ... 调用OrderService } }总结三大技术的协同工作流一个完整的秒杀请求处理流程是三大核心技术协同工作的结果请求入口用户发起秒杀请求。原子扣减服务端首先执行Redis Lua脚本原子性地扣减缓存中的库存。若失败库存不足直接返回失败。防重校验扣减成功后使用Redisson分布式锁锁定“用户-商品”维度执行“一人一单”等业务规则校验。异步落库校验通过后将订单信息作为消息发送至Redis Stream 或 RabbitMQ消息队列立即返回用户“秒杀成功”提示。最终消费独立的消费者服务从消息队列中取出任务异步、平稳地完成数据库订单创建、库存持久化扣减、发送短信通知等耗时操作。通过以上组合系统前端可以承受极高的QPS每秒查询率而将压力平滑地转移到后端异步处理在保证数据强一致性的同时极大地提升了系统的整体吞吐量和用户体验。参考来源Java高并发秒杀系统实战示例高并发环境下的UUID生成终极指南ramsey/uuid原子操作深度解析秒杀系统设计三件套RedisLuaStream消息队列保姆级配置高并发商品秒杀系统Java实战代码设计Java高并发秒杀系统完整项目实战代码Java高并发处理核心技术详解从理论到实战