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.

  1. EXISTS key: Returns 1 if the key EXISTS. Returns 0 if the given key does not exist.

  2. 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.

  3. GET key: Returns the string value associated with the key, or nil if the key does not exist.

  4. DEL key [key]… : Deletes one or more given keys. Non-existent keys are ignored and the number of deleted keys (integer) is returned.

  5. 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.

  6. HEXISTS Key field: Returns 1 if the key contains a field or 0 if at least one key or field does not exist.

  7. 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

  8. PEXPIRE key milliseconds: Sets the lifetime, in milliseconds. Expire operates in seconds.

  9. 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.