redis分布式锁

Posted ex-jindawei001

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis分布式锁相关的知识,希望对你有一定的参考价值。

上一篇写的是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分布式锁的主要内容,如果未能解决你的问题,请参考以下文章

redis 分布式锁

如何使用redis实现分布式锁功能?

分布式锁用zookeeper还是redis好

Redis分布式锁的原理是啥?如何续期?

分布式锁--Redis秒杀(互斥锁)(一)

Redis | 黑马点评 + 思维导图分布式锁