电商秒杀场景的核心挑战在于如何在高并发流量下保持系统稳定。传统架构中,秒杀系统崩溃的主要原因可以归纳为以下几点:
锁竞争问题:当使用synchronized或ReentrantLock等锁机制时,大量线程竞争同一资源会导致CPU时间大量消耗在线程上下文切换上,而非实际业务处理。
数据库瓶颈:直接对数据库进行高并发update/insert操作会迅速耗尽连接池资源,同时可能引发慢查询、死锁和行锁排队等问题。
无效请求处理:传统架构中,所有请求都会进入数据库处理层,导致大量最终会失败的请求也消耗了宝贵的数据库资源。
重复请求问题:用户频繁点击或网络重试会导致同一请求多次提交,造成资源浪费和业务逻辑混乱。
异步处理不可靠:使用普通消息队列时,可能出现消息积压、丢失或消费失败无补偿等问题。
技术选型要点:秒杀系统的核心不是业务逻辑复杂,而是如何在极端并发下高效过滤无效请求,只让少量可能成功的请求进入资源密集型处理流程。
Disruptor是一个高性能的线程间消息传递框架,其核心设计理念完全针对秒杀场景的需求:
Disruptor采用预分配的环形数组结构,避免了常规队列中节点频繁创建销毁带来的GC压力。在秒杀场景中,这意味着:
通过精心设计的序列号管理和内存屏障,Disruptor实现了:
这种设计在秒杀场景下可避免锁竞争导致的性能下降。
Disruptor支持批量获取和处理事件,这对秒杀订单的批量入库非常有利,可以:
code复制客户端层
↓
网关层(Nginx/API Gateway):实现限流、黑名单、验证码等防护
↓
应用服务层:核心业务逻辑处理
↓
Redis集群:
- 原子库存预减
- 用户去重判断
↓
Disruptor内存队列:顺序化处理成功请求
↓
数据库层:最终订单创建和库存扣减
↓
(可选)消息队列:异步处理履约、通知等下游逻辑
java复制// 库存键
String stockKey = "seckill:stock:" + skuId;
// 用户去重键
String userOrderKey = "seckill:order:" + skuId + ":" + userId;
// 订单结果键
String resultKey = "seckill:result:" + skuId + ":" + userId;
sql复制CREATE TABLE seckill_stock (
sku_id BIGINT PRIMARY KEY,
total_stock INT NOT NULL,
sold_stock INT NOT NULL DEFAULT 0,
version INT NOT NULL DEFAULT 0,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE seckill_order (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
sku_id BIGINT NOT NULL,
status TINYINT NOT NULL, -- 0=INIT 1=SUCCESS 2=FAIL
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_sku (user_id, sku_id) -- 防重最后防线
);
java复制public SeckillAcceptResult seckill(Long userId, Long skuId) {
// 1. 幂等检查
String existed = redis.get(resultKey);
if (existed != null) return SeckillAcceptResult.already(existed);
// 2. Redis原子预减库存
Long remaining = redis.execute(stockDeductScript, stockKey, "1");
if (remaining <= 0) {
return SeckillAcceptResult.outOfStock();
}
// 3. 用户去重检查
Boolean firstOrder = redis.setIfAbsent(userOrderKey, "1", TTL);
if (!firstOrder) {
redis.incr(stockKey); // 补偿库存
return SeckillAcceptResult.duplicate();
}
// 4. 投递Disruptor
boolean success = publisher.tryPublish(userId, skuId);
if (!success) {
// 队列满补偿
redis.incr(stockKey);
redis.delete(userOrderKey);
return SeckillAcceptResult.systemBusy();
}
return SeckillAcceptResult.accepted();
}
java复制public class SeckillEventHandler implements EventHandler<SeckillEvent> {
@Override
public void onEvent(SeckillEvent event, long sequence, boolean endOfBatch) {
transactionTemplate.execute(status -> {
try {
// 1. 创建订单
orderMapper.insert(event.getOrderId(),
event.getUserId(),
event.getSkuId());
// 2. 更新库存
stockMapper.deduct(event.getSkuId(), 1);
// 3. 更新Redis结果
redis.set(resultKey, event.getOrderId());
return true;
} catch (DuplicateKeyException e) {
// 唯一键冲突视为成功
return true;
} catch (Exception e) {
status.setRollbackOnly();
// 失败补偿
redis.incr(stockKey);
redis.delete(userOrderKey);
redis.set(resultKey, "FAIL");
return false;
}
});
}
}
lua复制-- 库存预减Lua脚本
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock <= 0 then return -1 end
redis.call('DECRBY', KEYS[1], ARGV[1])
return stock - ARGV[1]
合理设置TTL:
管道化操作:将多个Redis命令打包减少网络往返
java复制@Bean
public Disruptor<SeckillEvent> seckillDisruptor() {
// 缓冲区大小设置为2的幂次方
int bufferSize = 1 << 20; // 1048576
// 使用性能最优的等待策略
WaitStrategy waitStrategy = new YieldingWaitStrategy();
// 配置线程工厂
ThreadFactory threadFactory = r -> {
Thread t = new Thread(r);
t.setName("seckill-disruptor-" + t.getId());
t.setDaemon(true);
return t;
};
// 创建Disruptor实例
Disruptor<SeckillEvent> disruptor = new Disruptor<>(
SeckillEvent::new,
bufferSize,
threadFactory,
ProducerType.MULTI, // 支持多生产者
waitStrategy
);
// 设置异常处理器
disruptor.setDefaultExceptionHandler(new SeckillExceptionHandler());
return disruptor;
}
索引优化:
事务控制:
连接池配置:
java复制public boolean tryPublish(Long userId, Long skuId) {
try {
long sequence = ringBuffer.tryNext(); // 非阻塞获取序列号
SeckillEvent event = ringBuffer.get(sequence);
event.set(userId, skuId, idGenerator.nextId());
ringBuffer.publish(sequence);
return true;
} catch (InsufficientCapacityException e) {
return false; // 队列已满
}
}
java复制public class SeckillExceptionHandler implements ExceptionHandler<SeckillEvent> {
@Override
public void handleEventException(Throwable ex, long sequence, SeckillEvent event) {
// 记录错误日志
// 执行补偿逻辑
redis.incr("seckill:stock:" + event.getSkuId());
redis.delete("seckill:order:" + event.getSkuId() + ":" + event.getUserId());
redis.set("seckill:result:" + event.getSkuId() + ":" + event.getUserId(),
"FAIL:EXCEPTION");
}
}
关键指标监控:
动态降级策略:
对于多SKU的高并发场景,建议采用分片Disruptor方案:
java复制// 创建多个Disruptor实例
Disruptor<SeckillEvent>[] disruptors = new Disruptor[SHARD_COUNT];
// 根据skuId路由到特定分片
int shardIndex = (int)(skuId % SHARD_COUNT);
boolean success = disruptors[shardIndex].tryPublish(userId, skuId);
这种设计可以:
基准压测:
关键参数调优:
性能指标:
库存超卖:
队列处理延迟:
Redis连接不足:
| 特性 | Disruptor方案 | 传统MQ方案 |
|---|---|---|
| 吞吐量 | 百万级TPS | 万级TPS |
| 延迟 | 微秒级 | 毫秒级 |
| 可靠性 | 内存级,需额外保障 | 持久化保障 |
| 开发复杂度 | 中等 | 较低 |
| 适用场景 | 极高性能要求的短链路 | 需要可靠性的长链路 |
纯Redis方案虽然也能实现秒杀,但存在以下不足:
Disruptor方案在Redis快速过滤的基础上,增加了:
对于更复杂的秒杀场景,可以考虑多级库存体系:
Disruptor动态扩容:
Redis集群扩展:
结合容器化技术实现灵活部署:
在实际业务中,我们通过这套方案成功支撑了百万级QPS的秒杀活动,系统稳定性和业务指标都达到了预期目标。关键在于Redis的快速过滤和Disruptor的顺序化处理相结合,既保证了高性能,又确保了数据一致性。