Bohemian, love of life. Java Cultivator (wechat official ID: Java Cultivator), welcome to follow.
For the distributed lock used in the project for a simple example configuration and source code analysis, and enumerate some of the basic knowledge used in the source code, but there is no netty knowledge used in Redisson analysis.
This paper mainly explores the following aspects
-
Maven configuration
-
Simple example of RedissonLock
-
Source code to use the Redis command
-
Lua scripting semantics used in the source code
-
Source code analysis
Maven configuration
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> < version > 2.2.12 < / version > < / dependency > < the dependency > < groupId > com. Fasterxml. Jackson. Core < / groupId > < artifactId > Jackson - annotations < / artifactId > < version > server < / version > < / dependency >Copy the code
Simple example of RedissonLock
Redission supports four redis connection modes: single-node, primary/secondary, Sentinel, and Cluster. The Sentinel connection mode is used in the project.
If the redis server is not local, please pay attention to the permissions.
Sentinel configuration
Config config = new Config(); Config. UseSentinelServers (.) addSentinelAddress (127.0.0.1: "6479", "127.0.0.1:6489)." setMasterName (" master "). The setPassword (" password "). SetDatabase (0); RedissonClient redisson = Redisson.create(config);Copy the code
Simple to use
RLock lock = redisson.getLock("test_lock"); try{ boolean isLock=lock.tryLock(); if(isLock){ doBusiness(); }}catch(exception e){}finally{ lock.unlock(); }Copy the code
Source code to use the Redis command
Distributed locks mainly require the following redis commands, which are listed here. In the source code analysis section you can continue to refer to the operation meaning of the command.
-
EXISTS key: Returns 1 if the key EXISTS. Returns 0 if the given key does not exist.
-
GETSET Key Value: Sets the value of the given key to value and returns the old value of the key, an error if the key exists but is not a string, and nil if the key does not exist.
-
GET key: Returns the string value associated with the key, or nil if the key does not exist.
-
DEL key [key]… : Deletes one or more given keys. Non-existent keys are ignored and the number of deleted keys (integer) is returned.
-
HSET key field value: Sets a key with a combination of {field=value}. If the key is not present, the value is assigned and returns 1. If the field is present, the value is updated and returns 0.
-
HEXISTS Key field: Returns 1 if the key contains a field or 0 if at least one key or field does not exist.
-
HINCRBY Key Field increment: Increments the value of the specified field stored in the Hash object of the key. If the key key does not exist, a new one will be created that holds the hash object. If the field does not exist, it will be created before the current operation, and its value will be set to 0. The return value will be the increment value
-
PEXPIRE key milliseconds: Sets the lifetime, in milliseconds. Expire operates in seconds.
-
PUBLISH Channel Message: Posts a message to a channel and returns the number of clients that received the message.
Lua scripting semantics used in the source code
In Redisson source code, executing redis command is lua script, which mainly uses the following concepts.
-
Redis. Call () executes the redis command.
-
KEYS[1] refers to the first parameter in the script
-
ARGV[1] is the value of the first argument in the script
-
Nil has the same meaning as false in the return value.
Note that when Redis executes lua scripts, the equivalent of a Redis level lock cannot perform other operations, similar to atomic operations and a key point of Redisson’s implementation.
In addition, if an exception occurs during lua script execution or the Redis server directly breaks down, executing the redis log-based reply command will delete the commands already executed in the script from the logs.
Source code analysis
RLOCK structure
public interface RLock extends Lock, RExpirable { void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException; boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException; void lock(long leaseTime, TimeUnit unit); void forceUnlock(); boolean isLocked(); boolean isHeldByCurrentThread(); int getHoldCount(); Future<Void> unlockAsync(); Future<Boolean> tryLockAsync(); Future<Void> lockAsync(); Future<Void> lockAsync(long leaseTime, TimeUnit unit); Future<Boolean> tryLockAsync(long waitTime, TimeUnit unit); Future<Boolean> tryLockAsync(long waitTime, long leaseTime, TimeUnit unit); }Copy the code
Boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) ¶ tryLock(long waitTime, long leaseTime, TimeUnit unit) Redis forces you to unlock it if you have not unlocked it after leaseTime, which is 30 seconds by default
RedissonLock get lock tryLock source code
Future<Long> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId) { internalLockLeaseTime = unit.toMillis(leaseTime); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_LONG, "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; " + "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; " + "return redis.call('pttl', KEYS[1]);" , Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId)); }Copy the code
Among them:
-
KEYS[1] represents getName(), which represents the lock name test_lock
-
ARGV[1] specifies internalLockLeaseTime. The default value is 30s
-
ARGV[2] : getLockName(threadId) : ARGV[2] : getLockName(threadId) : ARGV[2] : getLockName(threadId) : ARGV[2] : getLockName(threadId) :
Sentence by sentence analysis:
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; 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;Copy the code
If (redis. Call (‘ exists’, KEYS[1]) == 0) If the lock name does not exist
Then redis. Call (‘ hset ‘, KEYS[1], ARGV[2],1) add a set with key test_lock to redis, and add a key pair with field as thread ID and value =1 to set, indicating that the thread reentrant count is 1
Redis. call(‘ pexpire ‘, KEYS[1], ARGV[1]) sets the expiration time of the set to prevent the current server from causing a deadlock, return nil; end; Return nil end
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;
Copy the code
If (redis. Call (‘ hexists’, KEYS[1], ARGV[2]) == 1) If the lock exists, check whether the current thread holds the lock, and if the current thread holds the lock
Then redis.call(‘ hincrby ‘, KEYS[1], ARGV[2], 1) then redis.call(‘ hincrby ‘, KEYS[1], ARGV[2], 1
Redis. Call (‘ pexpire ‘, KEYS[1], ARGV[1]) and reset the duration of the lock
return nil; end; Return nil, end of story
return redis.call('pttl', KEYS[1]);
Copy the code
If a lock exists but was not added by the current thread, the expiration time of the lock is returned.
RedissonLock Unlock source code
@Override public void unlock() { Boolean opStatus = commandExecutor.evalWrite(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('exists', KEYS[1]) == 0) then " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1; " + "end;" + "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " + "return nil;" + "end; " + "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " + "if (counter > 0) then " + "redis.call('pexpire', KEYS[1], ARGV[2]); " + "return 0; " + "else " + "redis.call('del', KEYS[1]); " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1; "+ "end; " + "return nil;", Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(Thread.currentThread().getId())); if (opStatus == null) { throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + Thread.currentThread().getId()); } if (opStatus) { cancelExpirationRenewal(); } }
Copy the code
Among them:
-
KEYS[1] indicates that getName() represents the lock name test_lock
-
KEYS[2] indicates that getChanelName() indicates the Chanel used in the publishing and subscription process
-
ARGV[1] represents LockPubSub. UnLockMessage represents the unLockMessage. ARGV[1] represents the unLockMessage
-
ARGV[2] indicates internalLockLeaseTime the default validity time is 30s
-
ARGV[3] is getLockName(thread.CurrentThread ().getid ()), the current lock ID + the thread ID
Semantic analysis:
if (redis.call('exists', KEYS[1]) == 0) then redis.call('publish', KEYS[2], ARGV[1]); return 1; end;
Copy the code
If (redis.call(‘ exists’, KEYS[1]) == 0)
Then redis. Call (‘ publish ‘, KEYS[2], ARGV[1]) publishes the message that the lock is released
return 1; End Returns 1 to end
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil; end;
Copy the code
If (redis. Call (‘ hexists’, KEYS[1], ARGV[3]) == 0) if the lock exists, but if the current thread is not the locked thread
then return nil; End just returns nil to end
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); return 0; else redis.call('del', KEYS[1]); redis.call('publish', KEYS[2], ARGV[1]); return 1; end;Copy the code
Local counter = redis.call(‘ hincrby ‘, KEYS[1], ARGV[3], -1
If (counter > 0) If the reentrant count is greater than 0, the thread has other tasks to execute
Then redis. Call (‘ pexpire ‘, KEYS[1], ARGV[2]) resets the valid time of the lock
Return 0 Returns 0. End
Else redis. Call (‘ del ‘, KEYS[1]) otherwise indicates that the thread is finished and the lock is deleted
Redis. Call (‘ publish ‘, KEYS[2], ARGV[1]) and publish the message that the lock is released
return 1; end; Return 1 end
return nil;
Copy the code
Other cases return nil and end
if (opStatus == null) { throw new IllegalMonitorStateException("attempt to unlock lock, not locked by current thread by node id: " + id + " thread-id: " + Thread.currentThread().getId()); }
Copy the code
After script execution, an exception is thrown if the return value is not 0 or 1, that is, when the current thread attempts to unlock another thread’s lock.
RedissonLock force unlock source code
@Override public void forceUnlock() { get(forceUnlockAsync()); } Future<Boolean> forceUnlockAsync() { cancelExpirationRenewal(); return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN, "if (redis.call('del', KEYS[1]) == 1) then " + "redis.call('publish', KEYS[2], ARGV[1]); " + "return 1 " + "else " + "return 0 " + "end", Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.unlockMessage); }
Copy the code
The forceUnlock() method is invoked many times, mostly to remove the lock while cleaning up the resource. If the lock is deleted successfully, a message is released indicating that the lock is deleted. Otherwise, a message is returned indicating that the lock is deleted.
conclusion
Here is just a simple test case of a Redisson distributed lock, and analysis of the execution of lua script this part, if you want to continue to analyze the end of the operation, the need for NetTY source analysis, Redisson used NetTY to complete the asynchronous and synchronous processing.