
客户端层 → 接入层 → 业务服务层 → 数据层
↓ ↓ ↓ ↓
限流 缓存 队列 数据库# 使用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 == 1UPDATE 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": 200,
"msg": "排队中",
"order_id": order_id,
"queue_position": 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原子扣减防超卖;服务无状态化应对高并发;再通过异步、对账等手段保证最终一致性和用户体验。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。