Java 设计支持万人并发抢购的秒杀系统方案
详细阐述了高并发秒杀系统的架构设计与核心问题解决方案。涵盖分层架构(客户端、接入层、业务层、数据层),重点解决超卖问题(Redis 原子操作、数据库乐观锁、预扣库存)及高并发处理(流量削峰、分层过滤)。此外还包括性能优化(多级缓存)、高可用保障(限流降级、熔断)、监控告警、部署扩展及安全考虑。最后提供面试回答思路,强调动静分离、请求队列化、Redis 原子扣减及最终一致性保证。

详细阐述了高并发秒杀系统的架构设计与核心问题解决方案。涵盖分层架构(客户端、接入层、业务层、数据层),重点解决超卖问题(Redis 原子操作、数据库乐观锁、预扣库存)及高并发处理(流量削峰、分层过滤)。此外还包括性能优化(多级缓存)、高可用保障(限流降级、熔断)、监控告警、部署扩展及安全考虑。最后提供面试回答思路,强调动静分离、请求队列化、Redis 原子扣减及最终一致性保证。

客户端层 → 接入层 → 业务服务层 → 数据层
限流 → 缓存 → 队列 → 数据库
-- 使用 Redis 的 DECR 原子操作扣减库存
def deduct_stock(product_id, user_id):
stock_key = f"stock:{product_id}"
# Lua 脚本保证原子性
lua_script = """
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock and stock > 0 then
redis.call('DECR', KEYS[1])
return 1
end
return 0
"""
result = redis.eval(lua_script, 1, stock_key)
return result == 1
UPDATE products SET stock = stock - 1, version = version + 1
WHERE id = ? AND stock > 0 AND version = ?
// 先预扣 Redis 库存,再异步同步到 DB
public boolean preDeductStock(String productId, int count) {
String key = "seckill:stock:" + productId;
Long remaining = redisTemplate.opsForValue().decrement(key, count);
if (remaining >= 0) {
// 发送 MQ 消息异步扣减数据库
sendStockDeductMessage(productId, count);
return true;
} else {
// 库存不足,回滚
redisTemplate.opsForValue().increment(key, count);
return false;
}
}
// 使用消息队列缓冲请求
@Component
public class SeckillService {
@Autowired
private RocketMQTemplate mqTemplate;
public SeckillResult seckill(SeckillRequest request) {
// 1. 校验用户和商品状态
if (!validate(request)) {
return SeckillResult.fail("校验失败");
}
// 2. 生成唯一请求 ID
String requestId = generateRequestId(request);
// 3. 请求入队,立即返回
mqTemplate.sendOneWay("seckill-topic", MessageBuilder.withPayload(request).build());
// 4. 返回排队中状态,前端轮询结果
return SeckillResult.processing(requestId);
}
}
所有请求 → 合法性校验 → 库存校验 → 频率控制 → 实际下单 ↓ ↓ ↓ ↓ ↓ 100 万 → 50 万 → 10 万 → 5 万 → 1 万
多级缓存配置
缓存层级:
一级:JVM 本地缓存 (Caffeine) - 热点商品
二级:Redis 集群 - 库存信息
三级:数据库 - 最终一致性
// 商品信息缓存预热
@Service
public class CacheWarmUpService {
@PostConstruct
public void warmUpSeckillProducts() {
List<Product> hotProducts = loadHotProducts();
for (Product product : hotProducts) {
// 库存信息
redisTemplate.opsForValue().set(
"stock:" + product.getId(), product.getStock()
);
// 商品详情
redisTemplate.opsForValue().set(
"product:" + product.getId(), JSON.toJSONString(product)
);
// 使用布隆过滤器存储可售商品 ID
bloomFilter.add(product.getId());
}
}
}
class SeckillSystem:
def process_seckill(self, user_id, product_id):
# 1. 恶意请求拦截
if not self.check_risk(user_id):
return {"code": 403, "msg": "访问过于频繁"}
# 2. 布隆过滤器快速判断
if not bloom_filter.contains(product_id):
return {"code": 404, "msg": "商品不存在"}
# 3. 内存标记(已售罄的商品直接返回)
if sold_out_flags.get(product_id):
return {"code": 400, "msg": "已售罄"}
# 4. Redis 原子扣减库存
if not self.deduct_stock_in_redis(product_id):
sold_out_flags[product_id] = True
return {"code": 400, "msg": "库存不足"}
# 5. 生成订单 ID(雪花算法)
order_id = snowflake.generate()
# 6. 订单信息入队
mq.send({
"order_id": order_id,
"user_id": user_id,
"product_id": product_id,
"time": time.time()
})
# 7. 返回排队中
return {
"code": , : ,
: order_id,
: get_queue_position(order_id)
}
@Component
@Slf4j
public class StockSyncService {
// 数据库最终扣减
@Transactional
public void syncStockToDB(String productId, int count) {
try {
// 数据库扣减(带重试机制)
boolean success = productDAO.deductStock(productId, count);
if (success) {
// 更新 Redis 中的最终库存状态
redisTemplate.opsForValue().set(
"stock_final:" + productId, getDBStock(productId)
);
// 删除售罄标记
soldOutCache.remove(productId);
}
} catch (Exception e) {
log.error("库存同步失败", e);
// 记录异常,人工介入处理
alertService.sendAlert(e);
}
}
// 库存对账任务
@Scheduled(cron = "0 */5 * * * ?")
public void stockReconciliation() {
List<Product> products = productDAO.getAllSeckillProducts();
for (Product product : products) {
Integer redisStock = getRedisStock(product.getId());
Integer dbStock = product.getStock();
if (!Objects.equals(redisStock, dbStock)) {
log.warn("库存不一致:productId={}, redis={}, db={}",
product.getId(), redisStock, dbStock);
// 自动修复或报警
fixStockInconsistency(product.getId(), dbStock);
}
}
}
}
多维度限流配置
限流规则:
用户维度:每个用户 10 次/分钟
IP 维度:每个 IP 1000 次/分钟
商品维度:每个商品 10000 次/分钟
总 QPS: 系统最大承受 50000 QPS
@RestController
@Slf4j
public class SeckillController {
@GetMapping("/seckill/{productId}")
@HystrixCommand(
fallbackMethod = "seckillFallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20")
}
)
public Response seckill(@PathVariable String productId, @RequestParam String userId) {
return seckillService.process(userId, productId);
}
// 降级方法
public Response seckillFallback(String productId, String userId) {
return Response.error("系统繁忙,请稍后重试");
}
}
@Component
public class SeckillMonitor {
private final MeterRegistry meterRegistry;
// 记录关键指标
public void recordSeckill(String productId, boolean success, long cost) {
// QPS 监控
meterRegistry.counter("seckill.requests.total").increment();
if (success) {
meterRegistry.counter("seckill.success.total").increment();
} else {
meterRegistry.counter("seckill.fail.total").increment();
}
// 耗时分布
meterRegistry.timer("seckill.process.time")
.record(cost, TimeUnit.MILLISECONDS);
// 库存变化
meterRegistry.gauge("seckill.stock." + productId, getCurrentStock(productId));
}
}
压测场景:
场景 1: 库存预热,10 万用户同时抢 1 万商品
场景 2: 持续高压,5 万 QPS 持续 5 分钟
场景 3: 峰值冲击,瞬间 20 万 QPS
压测目标:
成功率:>99.9%
平均 RT: <100ms
错误率:<0.1%
首先,架构设计上要动静分离、分层削峰。我会把系统分为:
其次,针对如何解决超卖、库存扣减和高并发请求这三个核心问题,我的解决方案是:
DECR 或 LUA 脚本来扣减库存。DECR 命令会直接返回扣减后的值,如果返回值小于 0,就说明库存没了,后续流程直接返回售罄。LUA 脚本可以打包多个操作(检查库存、扣减),确保整个过程原子性,彻底杜绝超卖。最后,还有一些关键的细节和兜底策略:
总结一下,我的设计思路是:前端限流拦截,请求队列削峰;Redis 原子扣减防超卖;服务无状态化应对高并发;再通过异步、对账等手段保证最终一致性和用户体验。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online