在秒杀系统中,库存超卖是一个关键问题,需要通过多种技术手段来保证高并发情况下库存的正确性。以下是几种常见的解决库存超卖的技术方案及其具体实现方法。
使用乐观锁可以防止多用户同时更新库存时导致超卖。乐观锁通常通过“版本号”机制来实现。
version
字段。version
是否与上次读取的一致,如果一致,则更新库存和 version
;如果不一致,则说明库存已经被其他用户修改过,需要重新尝试。-- 用户A在购买前读取库存和版本号
SELECT stock, version FROM products WHERE product_id = 1;
-- 用户A提交订单时,执行如下更新操作
UPDATE products
SET stock = stock - 1, version = version + 1
WHERE product_id = 1 AND version = 旧的version号;
如果 version
不匹配,则更新失败,用户需要重新尝试获取最新库存。
分布式锁可以确保在多台服务器上并发处理库存时不会导致超卖,常用 Redis 来实现分布式锁。
import redis
import time
r = redis.StrictRedis()
# 尝试获取锁,设置锁的有效期避免死锁
lock_key = "lock:product_1"
if r.set(lock_key, "locked", ex=5, nx=True): # ex 表示锁定时间,nx 表示键必须不存在
try:
# 执行扣库存操作
stock = r.get("product_stock_1")
if stock and int(stock) > 0:
r.decr("product_stock_1")
print("库存扣减成功")
else:
print("库存不足")
finally:
# 释放锁
r.delete(lock_key)
else:
print("获取锁失败,稍后重试")
在用户请求秒杀时,使用缓存进行库存预减(即在用户下单前就先减少库存),然后通过异步队列(如 Kafka 或 RabbitMQ)将订单请求发往后端进行异步处理。
# 假设秒杀商品的库存预存在 Redis 中
product_stock_key = "product_stock_1"
# 秒杀请求时,先从 Redis 中预减库存
stock = r.get(product_stock_key)
if stock and int(stock) > 0:
r.decr(product_stock_key)
# 将订单请求放入消息队列,进行后续处理
print("库存预减成功,订单处理中")
else:
print("库存不足,秒杀失败")
悲观锁通过直接锁定数据库中的某行数据,确保在高并发情况下只有一个用户可以修改库存。
在用户请求秒杀时,锁定库存行,直到操作完成后才释放锁。
-- 通过 FOR UPDATE 语句对库存行进行加锁,防止其他事务修改
SELECT stock FROM products WHERE product_id = 1 FOR UPDATE;
-- 更新库存
UPDATE products SET stock = stock - 1 WHERE product_id = 1;
提前生成秒杀令牌(Token)分发给用户,只有拿到令牌的用户才能参与秒杀,确保每个用户的请求是有限的,从而避免库存超卖。
# 秒杀令牌提前生成
token_key = "seckill_token_1"
r.set(token_key, 100) # 假设商品库存是 100
# 用户请求时,先获取令牌
if r.decr(token_key) >= 0:
print("成功获取令牌,继续下单流程")
else:
print("令牌获取失败,秒杀结束")
为了防止库存超卖,可以综合使用多种技术方案,常见的实现方式包括:
根据具体的业务场景和需求,可以选择合适的方案或将几种方案结合使用。