前言
redis本身属于弱事务,同一个线程向redis提交的指令,并不一定会紧挨着执行,这就会产生数据安全问题。
但由于redis是单线程执行指令的,所以只要能将多个指令打包到一起,就可以保证多个操作的原子性。并且redis是支持lua脚本的,会一次性将lua脚本中的所有指令全部顺序执行完毕,中间不会插入其他线程提交的指令。
鉴于以上特性,使用RedisTemplate+lua脚本实现一把可重入的分布式锁,且支持设定锁时间。
lua脚本准备
上锁的脚本
-- 加锁脚本
-- key1:要加锁的名称 argv1:锁存活的时间ms argv2:当前线程或主机的地址
local expire_time = tonumber(ARGV[1])
if redis.call('exists', KEYS[1]) == 0 then
-- 锁不存在,创建一把锁,存入hash类型的值
redis.call('hset', KEYS[1], ARGV[2], 1)
-- 设置锁的存活时间,防止死锁
redis.call('pexpire', KEYS[1], expire_time)
return 1
end
if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then
-- 表示是同一线程重入
redis.call('hincrby', KEYS[1], ARGV[2], 1)
-- 重新设置锁的过期时间
redis.call('pexpire', KEYS[1], expire_time)
return 1
end
-- 没抢到锁,返回锁的剩余有效时间ms
return redis.call('pttl', KEYS[1])
解锁的脚本
-- 解锁脚本
-- 判断是当前线程持有锁,避免解了其他线程加的锁
if redis.call('hexists',KEYS[1],ARGV[2]) == 1 then
-- 重入次数大于1,扣减次数
if tonumber(redis.call('hget',KEYS[1],ARGV[2])) > 1 then
return redis.call('hincrby', KEYS[1], ARGV[2], -1);
-- 重入次数等于1,删除该锁
else
return redis.call('del', KEYS[1]);
end
-- 判断不是当前线程持有锁,返回解锁失败
else
return 0;
end
代码
这里锁的逻辑仍采用我以前实现zk分布式锁的逻辑
实现redis分布式锁
package com.zyu.boot.demo.utils.lock.redis;
imp