SET key value[EX seconds][PX milliseconds][NX|XX]
key | 标志位 |
value | 唯一值,自己只能释放自己的锁 |
EX seconds | 设置过期时间,单位为秒 |
PX milliseconds | 设置过期时间,单位毫秒 |
NX | 仅当key不存在时设置值 |
XX | 仅当key存在设置值 |
public boolean tryLock(String key, String uniqueId, int seconds) { SetParams setParams = new SetParams(); setParams.ex(seconds); setParams.nx(); return "OK".equals(jedis.set(key, uniqueId, setParams)); }
uniqueId必须为唯一,解决的场景是:
public boolean deleteLock(String key, String value) { String luaScript = "if redis.call(‘get‘,KEYS[1]) == ARGV[1] then " + "return redis.call(‘del‘,KEYS[1]) else return 0 end"; return jedis.eval(luaScript, Collections.singletonList(key), Collections.singletonList(value)).equals(1L); }
这类缺点是加锁操作只作用在一个redis节点上,即使通过sentinel保证高可用,还是可能出现锁丢失,如以下情况:
redisson已经有对redlock算法封装
public void tryLock() { Config config = new Config(); config.useSentinelServers() .addSentinelAddress("127.0.0.1:6369", "127.0.0.1:6379", "127.0.0.1:6389") .setMasterName("masterName") .setPassword("password") .setDatabase(0); RedissonClient redissonClient = Redisson.create(config); RLock redLock = redissonClient.getLock("REDLOCK_KEY"); boolean isLock; try { //500ms获取锁的超时时间,10000ms是锁失效时间 isLock = redLock.tryLock(500, 10000, TimeUnit.MILLISECONDS); } catch (Exception e) { } finally { redLock.unlock(); } }
实现分布式锁的一个非常重要的点就是set的value要具有唯一性,redisson的value是怎样保证value的唯一性呢?答案是UUID+threadId
protected String getLockName(long threadId) { return id + ":" + threadId; }
获取锁
<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) { internalLockLeaseTime = unit.toMillis(leaseTime); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
//首先分布式锁key不存在,执行hset命令,并设置过期时间 "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; " +
//如果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; " +
// 获取分布式锁的KEY的失效时间毫秒数 "return redis.call(‘pttl‘, KEYS[1]);", Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); }
KEYS[1]就是Collections.singletonList(getName()),表示分布式锁的key,即REDLOCK_KEY;
ARGV[1]就是internalLockLeaseTime,即锁的租约时间,默认30s;
ARGV[2]就是getLockName(threadId),是获取锁时set的唯一值,即UUID+threadId:
释放锁
protected RFuture<Boolean> unlockInnerAsync(long threadId) { return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// 如果分布式锁存在,但是value不匹配,表示锁已经被占用,那么直接返回 "if (redis.call(‘hexists‘, KEYS[1], ARGV[3]) == 0) then " + "return nil;" + "end; " +
// 如果就是当前线程占有分布式锁,那么将重入次数减1 "local counter = redis.call(‘hincrby‘, KEYS[1], ARGV[3], -1); " +
//重入次数减1后的值如果大于0,表示分布式锁有重入过,那么只设置失效时间,还不能删除 "if (counter > 0) then " + "redis.call(‘pexpire‘, KEYS[1], ARGV[2]); " + "return 0; " + "else " +
//重入次数减1后的值如果为0,表示分布式锁只获取过1次,那么删除这个KEY,并发布解锁消息 "redis.call(‘del‘, KEYS[1]); " + "redis.call(‘publish‘, KEYS[2], ARGV[1]); " + "return 1; "+ "end; " + "return nil;", Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId)); }
原文:https://www.cnblogs.com/TripL/p/13346385.html