在Redis中,使用SETEX命令(对应RedisTemplate的setIfAbsent方法)可以实现一个最简易的分布锁。SETEX命令当key不存在的话,才会设置key的值,如果可以已经存在,就不做任何操作。
为了避免锁无法被释放,就给这个key(也就是锁)设置一个过期时间。
为了保证解锁操作的原子性,使用Lua脚本进行释放锁操作。
为了防止误删其他的锁,在Lua脚本中通过key对应的value(唯一值)来判断是否要释放锁。
最简单的分布式锁实现
不使用Lua脚本
// key 的唯一性可以关联业务
String key = "lockKey";
//value 的唯一性通过UUID生成
String value = UUID.randomUUID().toString().replaceAll("-", "");
//添加分布式锁,过期时间30秒
Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value, 300000, TimeUnit.MILLISECONDS);
logger.info("执行相关的业务逻辑操作");
//释放锁
redisTemplate.delete(key);
添加Lua脚本保证原子性
1.新建Lua脚本
释放锁时,先比较锁对应的 value 值是否相等,避免锁的误释放。
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
2.使用Lua脚本释放锁
logger.info("执行相关的业务逻辑操作");
//redisTemplate.delete(key);
DefaultRedisScript<Long> script = new DefaultRedisScript<Long>();
script.setResultType(Long.class);
script.setScriptSource(new ResourceScriptSource(new ClassPathResource("lock.lua")));
String lockValue = (String) redisTemplate.opsForValue().get(key);
if(lockValue != null && value.equals(lockValue)) {
List<String> keys = new ArrayList<>();
keys.add(key);
//执行脚本
Long execute = (Long)redisTemplate.execute(script, keys, value);
logger.info("execute=" + execute);
logger.info("释放分布锁,key=" + key);
}
参考资料
- https://www.cnblogs.com/niceyoo/p/13711149.html
- https://javaguide.cn/distributed-system/distributed-lock.html#分布式锁介绍