redis分布式锁相关


基于Redisson的分布式锁。

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

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

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

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

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

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

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

2、获取redissonClient

Config config = new Config();
config.useClusterServers().addNodeAddress(nodesFormater);
nodesFormater:redis://29.2.211.41:7000
RedissonClient redissonClient = Redisson.create(config);

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

RLock lock = redissonClient.getLock(lockName) lockName自定义
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.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.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比较