码迷,mamicode.com
首页 > 其他好文 > 详细

redis分布式锁

时间:2019-08-15 21:01:12      阅读:107      评论:0      收藏:0      [点我收藏+]

标签:其他   实例   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分布式锁机制,可放心使用.


redis分布式锁

标签:其他   实例   eof   匹配   release   value   单机   fast   Lua脚本   

原文地址:https://www.cnblogs.com/EX-JINDAWEI001/p/11360431.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!