标签:其他 实例 eof 匹配 release value 单机 fast Lua脚本
上一篇写的是mongo分布式锁的bug, 发现网上使用mongo实现分布式锁方案有bug, 目前我还找到解决方案, 建议大家还是使用redis来实现
具体思路还是利用redis的setnx方法的安全性, 同一时刻永远只有一个线程能set成功.
加锁代码如下:
public boolean lock(String lockKey, String requestId, Long expireTime) {
return stringRedisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.SECONDS);
//若不支持上述原子操作,可使用如下方式
// RedisScript redisScript = RedisScript.of(LOCK_LUA, Boolean.class);
// List<String> keys = new ArrayList<>();
// keys.add(lockKey);
// return (Boolean)stringRedisTemplate.execute(redisScript, new FastJsonRedisSerializer<>(Object.class),
// new FastJsonRedisSerializer<>(Object.class), keys, requestId, expireTime);
}
如注释中所述,若不支持上述原子操作,可使用LUA脚本方式实现,不过需要注意,如果redis是集群模式,是不支持以上lock方法中注释的代码的,
需要拿到原redis的connection来执行脚本,具体实现可参考下面unLock方法依葫芦画瓢.
低版本的redisTemplate是不支持setIfAbsent的同时设置过期时间的,
需要分两步,先setIfAbsent,成功后再expire,不过这样以来就不是同步的了,
也许在setIfAbsent成功后还没来得及expire,系统dump了或redis挂了,那就造成死锁了!!!
加锁代码如下:
/**
* 单机和集群都适用,
* 但有个条件,单机模式时,必须排除掉io.lettuce包,确保连接使用的是Jedis实例
* @param key
* @param requestId
* @return
*/
public boolean unLock(String key,String requestId) {
// 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
try {
List<String> keys = new ArrayList<>();
keys.add(key);
List<String> args = new ArrayList<>();
args.add(requestId);
// 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
// spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
Long result = stringRedisTemplate.execute((RedisCallback<Long>) connection -> {
Object nativeConnection = connection.getNativeConnection();
// 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
// 集群模式
if (nativeConnection instanceof JedisCluster) {
return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
}
// 单机模式
else if (nativeConnection instanceof Jedis) {
return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
}
return 0L;
});
return result != null && result > 0;
} catch (Exception e) {
logger.error("release lock error", e);
}
return false;
}
上述解锁代码注释已经很详细了,这个方法也是网上使用较多的, 加了部分自己的注释.
有个问题值得注意,就是当你的redis是单机模式时,必须排除掉io.lettuce包,确保连接使用的是Jedis实例,
否则进入不了单机模式的删锁代码,导致释放锁失效.
另,附上加锁和解锁的LUA脚本:
public static final String UNLOCK_LUA;
public static final String LOCK_LUA;
static {
StringBuilder unlockLua = new StringBuilder();
unlockLua.append("if redis.call(‘get‘, KEYS[1]) == ARGV[1] ");
unlockLua.append("then ");
unlockLua.append(" return redis.call(‘del‘, KEYS[1]) ");
unlockLua.append("else ");
unlockLua.append(" return 0 ");
unlockLua.append("end ");
UNLOCK_LUA = unlockLua.toString();
StringBuilder lockLua = new StringBuilder();
lockLua.append("if redis.call(‘setnx‘, KEYS[1], ARGV[1]) == 1 ");
lockLua.append("then ");
lockLua.append(" redis.call(‘expire‘, KEYS[1], ARGV[2]) return true ");
lockLua.append("else ");
lockLua.append(" return false ");
lockLua.append("end ");
LOCK_LUA = lockLua.toString();
}
以上代码完美实现redis分布式锁机制,可放心使用.
标签:其他 实例 eof 匹配 release value 单机 fast Lua脚本
原文地址:https://www.cnblogs.com/EX-JINDAWEI001/p/11360431.html