上一篇说了删锁存在原子性问题:
什么是原子性?
简单理解就是一件事情要么不做,要做就做全做好。
上一篇代码完成了基于redis 的setnx和加过期时间实现了分布式锁,但是因为判断锁是否一致和删锁是要执行两段代码,假如说我判断这是我这个线程的锁,好准备要删锁了,这时候服务宕机了或者停电了,是不是这个锁不是正常情况下释放的,虽然我们有过期时间兜底,那如果我们再换一种场景这个时候我们因为网络慢或者业务执行过长,导致锁到过期时间自动释放了,这时候我们第二个线程进来了他获取到锁了,准备开始执行业务操作了,但是我们上一个业务执行完了,好,他是不是要执行删除操作了,这时候问题来了,这个时候这把锁已经不是他自己的了,删掉别人的锁了,这个就是原子性问题。下面这个图简单就能很好的说明。
下面我们就要说到lua脚本了,因为他满足原子性操作。
什么是Lua脚本?
RedisTemplate已经为我们提供了API我们只要调用下面这个方法即可。
其中三个参数的含义是:script 就是你要执行的Lua脚本,keys是你要传入的锁的name,最后一个我们这里要传入的线程标识。
相关代码的实现
local voucherId=ARGV[1]
local userId=ARGV[2]
local storyKey='seckill:stock:'..voucherId
local orderKey='seckill:order:'..voucherId
--如果库存不足
if (tonumber(redis.call('get',storyKey))<=0) then
return 1
end
--判断是否已经下单
if (redis.call('SISMEMBER', orderKey, userId)==1) then
return 2
end
--两个条件都通过,扣库存,下单
redis.call('incrby',storyKey,-1)
redis.call('sadd',orderKey,userId)
return 0
//初始化lua脚本
private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
static {
SECKILL_SCRIPT = new DefaultRedisScript<>();
SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
SECKILL_SCRIPT.setResultType(Long.class);
}
调用RedisTemplate中的excute方法
Long result = stringRedisTemplate.execute(
SECKILL_SCRIPT,
Collections.emptyList(),
voucherId.toString(), userId.toString());
总结:
其实基于setnx加过期时间,已经算比较完备的解决分布式锁的解决方案了,但是其中还是其他问题
还会有哪些问题呢?下一篇我们来看看。
其中如有错误,请大家指出谢谢!