基于Redisson的分布式锁。
使用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);
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.<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比较