面试题答案
一键面试设计思路
- 使用Redis的单线程特性:利用Redis单线程执行命令的特性,保证一系列操作在Redis层面的原子性。
- Lua脚本:将库存扣减和订单生成相关操作封装在Lua脚本中,确保在执行脚本时这一系列操作不会被其他命令打断。
- Watch机制:在执行Lua脚本前,通过Watch命令监控库存相关的键,防止在脚本执行过程中库存被其他客户端修改。
使用的Redis命令
- Watch:用于监视一个或多个键,当事务执行时,如果被监视的键被其他客户端修改,事务将被打断。
- Multi:标记一个事务块的开始。
- Exec:执行所有事务块内的命令。
- Lua脚本相关命令(EVAL或EVALSHA):用于执行Lua脚本。
原理
- Watch原理:Watch命令会在Redis服务端为每个客户端维护一个被监视键的列表。当被监视的键被修改时,Redis会标记这个客户端的事务需要被取消。在执行Exec命令时,Redis会检查事务对应的客户端所监视的键是否被修改,如果有修改则不执行事务。
- Lua脚本原理:Redis执行Lua脚本时,会将Lua脚本作为一个整体执行,不会被其他命令打断。在Lua脚本中可以通过Redis的API操作Redis数据,这样就可以将库存扣减和订单生成操作作为一个原子操作进行处理。
以下是一个简单的Lua脚本示例(假设库存键为stock_key
,订单生成相关操作通过向一个订单队列order_queue
中添加数据模拟):
-- 获取库存键的值
local stock = redis.call('GET', KEYS[1])
-- 判断库存是否足够
if tonumber(stock) >= tonumber(ARGV[1]) then
-- 扣减库存
redis.call('DECRBY', KEYS[1], ARGV[1])
-- 生成订单,这里简单模拟为向订单队列添加数据
redis.call('RPUSH', KEYS[2], ARGV[2])
return 1
else
return 0
end
在客户端代码中,先使用Watch命令监视库存键,然后调用EVAL(或EVALSHA)命令执行Lua脚本,例如在Python中使用redis - py
库:
import redis
r = redis.Redis(host='localhost', port=6379, db = 0)
# 监视库存键
r.watch('stock_key')
try:
stock = r.get('stock_key')
if stock is not None and int(stock) >= 1:
script = """
local stock = redis.call('GET', KEYS[1])
if tonumber(stock) >= tonumber(ARGV[1]) then
redis.call('DECRBY', KEYS[1], ARGV[1])
redis.call('RPUSH', KEYS[2], ARGV[2])
return 1
else
return 0
end
"""
result = r.eval(script, 2,'stock_key', 'order_queue', 1, 'order_info')
print(result)
else:
print('库存不足')
finally:
r.unwatch()