面试题答案
一键面试运用Redis分布式锁分段设计优化库存扣减过程及避免超卖问题
- 总体思路:将库存按照一定规则分成多个段,每个段使用独立的Redis分布式锁进行控制。当有库存扣减请求时,先确定请求对应的库存段,获取该段的锁,成功获取锁后进行库存扣减操作,操作完成后释放锁。这样可以在高并发场景下,减少锁的竞争,提高系统的并发处理能力。
- 具体实现步骤:
- 确定分段规则:可以根据商品ID、仓库区域、库存批次等属性对库存进行分段。例如,按照商品ID的哈希值对库存进行分段,将不同商品的库存划分到不同段中,使得每个段管理一部分商品的库存。
- 获取锁:在进行库存扣减前,根据请求的库存所属段,通过Redis的SETNX(SET if Not eXists)命令尝试获取该段的锁。例如,使用
SETNX lock_key value
命令,其中lock_key
为对应库存段的锁标识,value
可以是一个唯一标识(如UUID),用于后续释放锁时验证。如果SETNX命令返回1,表示成功获取锁;返回0,则表示锁已被其他请求持有,需要等待或重试。 - 库存扣减:获取锁成功后,对该段的库存进行扣减操作。可以通过Lua脚本来保证库存扣减操作的原子性,例如在Lua脚本中使用
redis.call('DECRBY', stock_key, quantity)
命令,其中stock_key
为库存键,quantity
为扣减数量。 - 释放锁:库存扣减操作完成后,通过Lua脚本使用
if redis.call('GET', lock_key) == value then return redis.call('DEL', lock_key) else return 0 end
命令释放锁,确保只有获取锁的请求才能释放锁。
分段依据
- 业务逻辑:根据电商系统的业务特点进行分段,如按照商品分类、仓库位置等业务属性划分。这样不同业务模块的库存扣减可以并行处理,减少锁冲突。例如,对于不同品类的商品,其库存扣减操作互不影响,可以分别划分到不同段中。
- 数据量和访问频率:考虑库存数据量和扣减请求的访问频率。如果某些商品或区域的库存访问频率高,可以将其单独划分成段,避免与其他低频率访问的库存段竞争锁。例如,热门商品的库存单独分段,以提高并发处理能力。
- 系统架构:结合系统的架构设计,如分布式存储的节点划分、负载均衡策略等进行分段。确保每个段的锁操作在系统架构层面易于管理和维护,并且能够充分利用系统资源。
锁的粒度选择
- 粗粒度锁:如果库存段划分较粗,一个段包含大量库存或多种商品库存,锁的粒度就较大。优点是锁的管理成本低,适合并发量相对较小、业务逻辑简单的场景,因为较少的锁操作可以减少锁竞争的开销。但缺点是并发处理能力有限,因为同一时间只有一个请求能获取粗粒度锁进行库存操作,容易成为系统瓶颈。例如,将整个仓库的所有商品库存作为一个段,使用一个锁,这就是粗粒度锁。
- 细粒度锁:当库存段划分很细,每个段只包含少量库存或单个商品的部分库存时,锁的粒度就较小。细粒度锁可以提高系统的并发处理能力,因为多个请求可以同时获取不同细粒度锁进行库存操作。但缺点是锁的管理成本高,需要频繁获取和释放锁,增加了系统开销,同时也可能导致死锁等问题。例如,将每个商品的每个SKU库存作为一个段,每个段对应一个锁,这就是细粒度锁。
- 选择原则:综合考虑系统的并发量、业务复杂度、锁的管理成本等因素来选择锁的粒度。一般来说,在高并发且业务复杂的电商系统中,倾向于选择相对细粒度的锁,但要通过合理的设计和优化来控制锁的管理成本;在并发量较低、业务简单的场景下,可以选择粗粒度锁以降低系统开销。