电商系统如何保证扣减库存高并发性能,又保证不会出现超卖问题
在电商系统中,保证高并发下的库存扣减性能并避免库存超卖的情况是一个关键问题,通常通过以下几种策略进行处理:
解决方案
1. 数据库层面的乐观锁
- 机制:在扣减库存时,使用版本号或时间戳来实现乐观锁,避免多个请求同时修改同一条数据。更新时会判断库存的版本号或时间戳是否与请求前一致,只有一致时才执行更新操作。
- 适用场景:并发量适中、库存修改频率相对较低的场景。
- 示例
UPDATE inventory
SET stock = stock - 1, version = version + 1
WHERE item_id = ? AND stock > 0 AND version = ?;如果返回的行数为 0,则说明扣减失败,需要进行重试。
2. 数据库事务与悲观锁
- 机制:使用数据库的事务隔离机制或者
SELECT FOR UPDATE来锁定库存记录,在事务内完成扣减操作。 - 优点:保证了操作的原子性,防止并发修改。
- 缺点:高并发下容易造成数据库锁竞争,降低系统吞吐量。
- 适用场景:并发量不高的场景。
- 示例
BEGIN;
SELECT stock FROM inventory WHERE item_id = ? FOR UPDATE;
UPDATE inventory SET stock = stock - 1 WHERE item_id = ? AND stock > 0;
COMMIT;3. 分布式锁
机制:在高并发下使用分布式锁(如 Redis 的
SETNX命令)来锁定操作,防止多个实例同时操作同一库存。优点:简单易实现,适用于分布式系统中不同节点并发操作。
缺点:锁粒度过大会影响系统吞吐量,分布式锁操作本身可能存在延迟或故障。
Redis 实现示例
使用 Redis 的
SETNX实现锁定:
SETNX lock_key unique_value- 成功后执行扣减操作,完成后释放锁。
4. 库存预扣减与最终一致性
- 机制:在订单创建时先进行库存预扣减,记录一个冻结的库存值,只有支付成功后才正式扣减库存。如果支付失败或订单取消,则释放冻结的库存。
- 优点:降低了扣减库存的时间窗口,减少并发竞争的概率。
- 缺点:需要设计冻结库存的状态和超时机制,增加了复杂度。
- 适用场景:高并发、高流量的电商场景。
- 步骤
- 订单创建时预扣减库存,更新冻结库存;
- 支付完成后正式扣减库存;
- 支付失败或超时后释放冻结库存。
5. 消息队列削峰
- 机制:将扣减库存的操作放入消息队列中,异步处理高并发的请求,削峰填谷,降低直接访问数据库的并发压力。
- 优点:通过异步处理将高并发操作分摊,避免数据库直接承受高并发请求。
- 缺点:有一定的延迟,并且需要保证消息处理的可靠性。
- 适用场景:极高并发的秒杀、促销场景。
6. 库存缓存
- 机制:使用 Redis 等缓存系统将库存数据放入内存中,利用缓存的高读写性能来提升扣减库存的并发性能,并定期同步缓存与数据库的数据一致性。
- 优点:提高了并发处理能力,缓存的高性能读写使系统能承受更多并发请求。
- 缺点:需要处理缓存和数据库的双写一致性问题,可能引入超卖问题。
- 解决方案:可以使用 Redis 的
INCRBY和 Lua 脚本原子性执行扣减操作。 - Redis Lua 脚本示例
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) > 0 then
return redis.call('DECR', KEYS[1])
else
return -1
end7. 数据库水平分库分表
- 机制:将库存数据进行分库分表,减少单个数据库表的并发压力。
- 优点:有效提升数据库的并发处理能力,适合大规模电商平台。
- 缺点:分库分表后的事务处理和数据一致性较为复杂。
- 适用场景:超大规模电商系统。
在电商系统中,为了在高并发场景下有效处理库存扣减并避免库存超卖,可以采用一个完整的方案,包括缓存、数据库、消息队列和锁机制等技术的组合。以下是一个比较完整的方案设计:
总体思路
- 库存缓存: 使用 Redis 等高性能缓存系统来处理扣减库存,提升并发处理能力。
- 消息队列: 通过消息队列异步化库存操作,削峰填谷,防止数据库直接承受高并发压力。
- 数据库乐观锁: 在数据库层面使用乐观锁来保证最终数据一致性,防止超卖。
- 库存预扣减: 在订单创建时冻结库存,支付成功后才正式扣减,保证在支付流程中的库存状态一致性。
- 分布式锁: 避免多个请求同时操作同一商品库存,保证分布式环境下的扣减安全。
1. 流程设计
1.1 订单创建时的库存预扣减
当用户提交订单时,系统首先进行库存预扣减,确保该订单有足够库存来进行后续支付流程。这个步骤主要分成以下几步:
- Redis 缓存扣减:首先从 Redis 缓存中扣减库存,利用 Redis 的高性能来提升并发处理能力。
Redis 扣减逻辑
如果 Redis 中的库存数量大于 0,则执行扣减操作,并将该扣减操作标记为 “冻结库存”。
使用 Redis Lua 脚本来保证扣减操作的原子性:
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) > 0 then
redis.call('DECRBY', KEYS[1], ARGV[1]) -- 扣减库存
redis.call('INCRBY', KEYS[2], ARGV[1]) -- 增加冻结库存
return 1
else
return 0
end- 数据库中的乐观锁更新:为了防止超卖,系统将异步将 Redis 缓存的库存状态同步到数据库中。使用乐观锁来确保扣减库存的安全性。
- 乐观锁实现: 在库存表中使用一个
version字段,结合 version号来控制并发更新。
UPDATE inventory
SET stock = stock - ?, version = version + 1
WHERE item_id = ? AND stock >= ? AND version = ?;- 订单冻结库存记录:在订单表中记录该笔订单冻结的库存数量,状态为 “冻结中”。如果支付成功,则进入库存扣减流程;如果支付失败或超时,系统会自动释放冻结的库存。
1.2 库存正式扣减
当用户支付成功时,系统需要正式扣减库存。这个步骤可以通过以下流程来实现:
- 确认订单支付状态:如果用户支付成功,系统会从 Redis 中的冻结库存中正式扣减库存,同时在数据库中执行库存同步操作。
- Redis 正式扣减:
- 支付成功后,减少冻结库存,增加实际扣减库存。
- Lua 脚本保证 Redis 中的扣减操作是原子性的:
local frozen_stock = redis.call('GET', KEYS[1])
if tonumber(frozen_stock) >= ARGV[1] then
redis.call('DECRBY', KEYS[1], ARGV[1]) -- 扣减冻结库存
redis.call('INCRBY', KEYS[2], ARGV[1]) -- 增加扣减后的库存
return 1
else
return 0
end- 数据库库存同步:
- 使用消息队列异步将最终的库存变化持久化到数据库中。
- 每次扣减数据库库存时,依然使用乐观锁防止并发问题:
UPDATE inventory
SET stock = stock - ?, version = version + 1
WHERE item_id = ? AND version = ?;1.3 支付失败的处理
如果支付失败或订单超时,系统需要释放冻结的库存。处理流程如下:
- 释放 Redis 中的冻结库存:
- 系统通过定时任务或消息机制监控订单支付状态。如果订单未支付成功,解冻 Redis 中的冻结库存,恢复可售库存。
- Lua 脚本保证解冻的原子性:
local frozen_stock = redis.call('GET', KEYS[1])
if tonumber(frozen_stock) >= ARGV[1] then
redis.call('INCRBY', KEYS[2], ARGV[1]) -- 恢复库存
redis.call('DECRBY', KEYS[1], ARGV[1]) -- 减少冻结库存
return 1
else
return 0
end- 数据库同步库存恢复:
- 同样使用消息队列将库存恢复操作同步到数据库中,使用乐观锁控制并发更新,确保数据一致性。
2. 消息队列削峰
为了应对大促销活动或秒杀场景下的极高并发压力,系统通过消息队列对请求进行削峰填谷处理。所有库存扣减和释放的操作都放入消息队列中,由消费者异步处理,减少数据库直接承受的并发压力。
- 库存操作入队:当 Redis 执行扣减或释放库存时,操作会被封装成消息,发送到消息队列。
- 消费者处理库存更新:消费者异步从队列中获取消息,处理数据库的库存同步操作,保证数据库和 Redis 之间的一致性。
3. 分布式锁控制超卖
在极端高并发的场景下,可以使用分布式锁进一步保证扣减库存的安全性。比如使用 Redis 的 SETNX 或者使用 Zookeeper 等工具来锁定某个商品的扣减操作,防止并发请求操作同一商品。
- Redis 锁示例
锁成功后,再执行库存操作,确保在高并发情况下,多个请求不会同时扣减同一个商品的库存。
SETNX product_lock_<product_id> unique_value EX 54. 性能优化与监控
为了提升系统性能和可靠性,还需要进行一些优化措施和监控手段:
- 库存缓存失效机制:缓存层与数据库同步时,可以通过定时任务或者变更监听来更新 Redis 中的库存缓存,避免缓存穿透和缓存雪崩问题。
- 监控与告警:通过监控 Redis 和数据库的库存状态、队列积压情况,及时发现问题,防止库存超卖或队列积压过多。