redis分布式锁相关

基于Redisson的分布式锁。

从多个节点获取锁,当一半以上的节点成功获取锁,才算成功获取锁。

redisson不会产生死锁问题,原因: redisson 有个开门狗机制,每隔10s会设置锁的有效期为30s

使用apollo的addChangeListener方法监听配置。

Redis Pttl 命令以毫秒为单位返回 key 的剩余过期时间。
pexpire 以毫秒为单位设置 key 的生存时间
Psetex 命令以毫秒为单位设置 key 的生存时间

1、通过以下方法创建redis集群实例

JedisCluster(Set<HostAndPort> jedisClusterNode, int connectionTimeout, int soTimeout,
int maxAttempts, String password, final GenericObjectPoolConfig poolConfig)

Set<HostAndPort> jedisClusterNode:redis服务器的端口和IP
connectionTimeout:链接超时时间
soTimeout:读超时
maxAttempts:最大重试次数
password:密码
poolConfig:redis池配置

2、获取redissonClient

Config config = new Config();
//默认读命令只可以在从节点上面执行,此时一旦服务器故障,主从节点切换,从节点不可访问时,就会导致应用的读命令无法执行
config.useClusterServers().addNodeAddress(nodesFormater).setPassword(password).setReadMode(MASTER_SLAVE);
nodesFormater:redis://29.2.211.41:7000
RedissonClient redissonClient = Redisson.create(config);

3、根据redissonClient获取锁、释放锁

RLock lock = redissonClient.getLock(lockName) lockName自定义
if(!lock.tryLock(6,TimeUtil.SECONDS)){
  return;//分布式锁已经被占用
}
doSomething();
finally{
if(lock.isLocked()&&lock.isHeldByCurrenThread()){
   lock.unlock();
}
}

向Redis通过EVAL命令执行LUA脚本即可
redLock:多个相互独立没有集群协调机制或者主从复制关系的Redis Master 可重入锁
获取锁:
1、获取锁时向5个redis实例发送的命令
1.1 首先分布式锁的KEY不能存在,如果确实不存在,那么执行hset命令(hset REDLOCK_KEY uuid+threadId 1),并通过pexpire设置失效时间(也是锁的租约时间)
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hset', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
1.2 如果分布式锁的KEY已经存在,并且value也匹配,表示是当前线程持有的锁,那么重入次数加1,并且设置失效时间
"if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
"redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
"redis.call('pexpire', KEYS[1], ARGV[1]); " +
"return nil; " +
"end; " +
1.3 获取分布式锁的KEY的失效时间毫秒数
"return redis.call('pttl', KEYS[1]);",
备注:这三个参数分别对应KEYS[1],ARGV[1]和ARGV[2]
Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));

释放锁:
2、向5个redis实例都执行如下命令
2.1 如果分布式锁KEY不存在,那么向channel发布一条消息
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end;" +
2.2如果分布式锁存在,但是value不匹配,表示锁已经被占用,那么直接返回
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
2.3如果就是当前线程占有分布式锁,那么将重入次数减1
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
2.3.1重入次数减1后的值如果大于0,表示分布式锁有重入过,那么只设置失效时间,还不能删除
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
2.3.2重入次数减1后的值如果为0,表示分布式锁只获取过1次,那么删除这个KEY,并发布解锁消息
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; "+
"end; " +
"return nil;",
备注:这5个参数分别对应KEYS[1],KEYS[2],ARGV[1],ARGV[2]和ARGV[3]
Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));

1、set命令要用set key value px milliseconds nx;

String jedisResult = jedis.set(key, value, "NX", "PX", expireTimeMillis);
boolean tryLockResult = "OK".equals(jedisResult);

nx:当key不存在时,设置key的值,px milliseconds:并将key值的过期时间设置为milliseconds毫秒

此命令保证了原子操作:setNX+expireTime


2、value要具有唯一性;UUID+threadId
3、释放锁时要验证value值,不能误解锁:通过key获取value,再与给定的value比较

 

 





 





posted @ 2021-05-27 11:26  龙之谷2019  阅读(42)  评论(0编辑  收藏  举报