在构建高并发秒杀系统时,确保系统在高流量冲击下仍能保持高性能、高可用和数据一致性是核心目标。经过对业界主流方案的梳理,可以提炼出三大核心技术支柱:原子性库存扣减、分布式锁防超卖、以及异步消息队列解耦。下面将结合具体技术实现和实战代码,详细解析这三大技术。
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 DefaultRedisScript<Long> 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 Stream | RabbitMQ |
|---|---|---|
| 部署复杂度 | 低(与Redis一体) | 中(需独立部署) |
| 功能丰富度 | 基础消息队列功能 | 丰富(多种交换机、路由、ACK机制) |
| 可靠性 | 依赖Redis持久化 | 非常高(持久化、确认、事务) |
| 适用场景 | 轻量级解耦、实时流处理 | 复杂业务解耦、高可靠异步任务 |
实战代码示例 (Redis Stream):
// 生产者:秒杀成功后,将订单任务放入Stream @Service public class SeckillProducerService { @Autowired private StringRedisTemplate redisTemplate; public void produceOrderTask(SeckillMessage message) { String streamKey = "stream:seckill:orders"; Map<String, 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) { // 从消费者组读取待处理消息 List<MapRecord<String, 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 (MapRecord<String, Object, Object> record : records) { Map<Object, 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原子操作深度解析
- 秒杀系统设计三件套:Redis+Lua+Stream消息队列保姆级配置
- 高并发商品秒杀系统Java实战代码设计
- Java高并发秒杀系统完整项目实战代码
- Java高并发处理核心技术详解:从理论到实战