The concept of distributed locks

Distributed locking is a way to control synchronous access to shared resources between distributed systems.

In distributed systems, they often need to coordinate their actions. If different systems or hosts on the same system share one or a group of resources, the access to these resources must be mutually exclusive to prevent interference and ensure consistency. In this case, distributed locks are required.

To implement a basic distributed lock, three features need to be met:

  1. Exclusive: Mutually exclusive, only one client holds the lock at any one time.
  2. Deadlock free: Even if the client holding the lock crashes or the network gets partitioned, the lock can still be obtained.
  3. Fault tolerance: Clients can acquire and release locks as long as most Redis nodes are alive.

If you want to implement a full distributed lock, you need to ensure that:

  1. In distributed systems, a method can only be executed by one thread on a machine at a time.
  2. Highly available lock acquisition and lock release;
  3. High-performance lock acquisition and lock release;
  4. With reentrant characteristics;
  5. Lock failure mechanism to prevent deadlocks;
  6. It has the non-blocking lock feature, that is, if the lock is not obtained, the system returns a failure to obtain the lock.

At present, there are three most mainstream implementation methods of distributed lock:

  1. Distributed lock based on database;
  2. Distributed lock based on cache (Redis, etc.);
  3. Distributed lock based on Zookeeper;

A simple implementation of Redis distributed lock

The simplest way to implement distributed locks in Redis is to create a key in Redis that has an expiration time (TTL) to ensure that the lock will eventually be released automatically (without deadlocks). This key is deleted when the client releases the resource (unlock).

Implementation of the SETNX command

SETNX KEY_NAME VALUE
Copy the code

This command sets the specified value for the key if the specified key does not exist. This command cannot set an EXPIRE time for the lock, so you need to use the EXPIRE command to set an automatic EXPIRE time for the lock.

EXPIRE KEY_NAME 1 // Unit is sCopy the code

A serious problem with this implementation is that the atomic operation of the lock cannot be guaranteed, because there is a probability that the SET command will be executed but the EXPIRE command will not be executed and the lock will not be released.

Implementation of the SET command

Starting with Redis 2.6.12, the behavior of the SET command can be modified with a series of parameters:

  • EX second: Sets the expiration time of the key to second seconds. SET key value EX second is the same as SETEX key second value.
  • PX millisecond: Set the expiration time of the key to millisecond. SET key value PX millisecond = PSETEX key millisecond value
  • NX: Sets the key only when the key does not exist. SET key value NX is the same as SETNX key value.
  • XX: Configures the key only when the key already exists.

Setnx and expire can now be combined into a single instruction without worrying about atoms.

SET KEY_NAME VALUE NX PX 30000
Copy the code

This command can only be executed if there is no key (NX option), and the key has a 30 second auto-expiration time (PX attribute). The value of this key must be unique on all clients. The value must not be the same for all competitors of the same key.

Release the lock

The correct way to release the lock would be to use the Lua script, which tells me that the deletion was successful only if the key exists and stores the same value as the one I specified.

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
Copy the code

Releasing locks in this way avoids deleting locks that other clients have successfully obtained.

For example: Client A acquires the resource lock, but is blocked by another operation. When client A tries to release the lock after completing other operations, the original lock has timed out and is automatically released by Redis. In the meantime, client B acquires the resource lock again. If the key was removed using the DEL command alone, this would remove the lock from client B. This is not the case with Lua scripts, because the script only removes the key whose value is equal to the value of client A (value is equivalent to A signature on the client side), and Lua scripts guarantee atomic execution.

Redisson distributed lock implementation

Redisson is a Java in-memory Data Grid based on Redis. It not only provides a set of distributed Java common objects, but also provides a number of distributed services. These include BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Redisson provides the simplest and most convenient way to use Redis. The goal of Redisson is to promote the Separation of Concern for Redisso that users can focus more on business logic.

Redisson is based on the Netty framework. Redis 2.8+ and Java1.6+ are supported.

Reentrant Lock

Based on Redis Redisson distributed reentrant Lock RLock Java object implements the Java. Util. Concurrent. The locks. Lock interface. It also provides Async, Reactive and RxJava2 standard interfaces.

RLock lock = redisson.getLock("anyLock"); 
// The most common way to use it
lock.lock();
Copy the code

If the Redisson node that stores the distributed lock goes down and the lock happens to be in the locked state, the lock will become locked. To prevent this from happening, Redisson internally provides a lock watchdog that continuously extends the lifetime of the lock before the Redisson instance is closed. By default, the watchdog check lock timeout time is 30 seconds, but can be by modified Config. LockWatchdogTimeout to specify separately.

In addition, Redisson provides the leaseTime parameter to specify the lock time through the lock method. After this time, the lock unlocks automatically.

// It will be unlocked in 10 seconds
// You do not need to call the unlock method
lock.lock(10, TimeUnit.SECONDS);

// Try locking. Wait 100 seconds at most. After locking, it will be unlocked automatically within 10 seconds
boolean res = lock.tryLock(100.10, TimeUnit.SECONDS);
if (res) {
   try{... }finally{ lock.unlock(); }}Copy the code

Redisson also provides a related method for asynchronous execution of distributed locks:

RLock lock = redisson.getLock("anyLock");
lock.lockAsync();
lock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = lock.tryLockAsync(100.10, TimeUnit.SECONDS);
Copy the code

The RLock object fully conforms to the Java Lock specification. That is only a process of lock can unlock, other processes to unlock will throw IllegalMonitorStateException errors. However, if you need other processes to unlock, use distributed Semaphore objects.

Fair Lock

RLock fairLock = redisson.getFairLock("anyLock");
// The most common way to use it
fairLock.lock();
Copy the code

It ensures that when multiple Redisson client lines request locking at the same time, the thread that made the request first is preferentially allocated. All requesting threads are queued in a queue, and when a thread goes down, Redisson waits five seconds before moving on to the next thread, meaning that if the first five threads are waiting, the next one will wait at least 25 seconds.

MultiLock

The Redisson multilock object can associate multiple RLock objects into an interlock, and each RLock object instance can come from a different Redisson instance.

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// Simultaneously lock: lock1 lock2 lock3
// All locks are locked successfully.lock.lock(); . lock.unlock();Copy the code

RedLock

Redisson Redlock object based on Redis realizes the locking algorithm introduced by Redlock. This object can also be used to associate multiple RLock objects into a red lock, and each RLock object instance can come from a different Redisson instance.

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// Simultaneously lock: lock1 lock2 lock3
// Red lock Success on most nodes.lock.lock(); . lock.unlock();Copy the code

ReadWriteLock

Based on Redis Redisson distributed re-entrant read-write lock RReadWriteLock Java object implements the Java. Util. Concurrent. The locks. ReadWriteLock interface. Read and write locks inherit the RLock interface.

Distributed reentrant read-write locks allow multiple read locks and one write lock to be locked at the same time.

RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// The most common way to use it
rwlock.readLock().lock();
/ / or
rwlock.writeLock().lock();
Copy the code

Semaphore

Based on Redis Redisson distributed signal (Semaphore) Java objects RSemaphore adopted with Java. Util. Concurrent. The Semaphore similar interface and usage. It also provides Async, Reactive and RxJava2 standard interfaces.

RSemaphore semaphore = redisson.getSemaphore("semaphore");
semaphore.acquire();
/ / or
semaphore.acquireAsync();
semaphore.acquire(23);
semaphore.tryAcquire();
/ / or
semaphore.tryAcquireAsync();
semaphore.tryAcquire(23, TimeUnit.SECONDS);
/ / or
semaphore.tryAcquireAsync(23, TimeUnit.SECONDS);
semaphore.release(10);
semaphore.release();
/ / or
semaphore.releaseAsync();
Copy the code

Permitable Semaphore

Redisson Expiration Semaphore based on Redis permitABlesemaphore is an expiration time added to each signal on the basis of RSemaphore object. Each signal can be identified by its own ID, which can only be released by submitting the ID. It provides interfaces to Async, Reactive, and RxJava2 standards.

RPermitExpirableSemaphore semaphore = redisson.getPermitExpirableSemaphore("mySemaphore");
String permitId = semaphore.acquire();
// Get a signal, valid for 2 seconds.
String permitId = semaphore.acquire(2, TimeUnit.SECONDS);
// ...
semaphore.release(permitId);
Copy the code

CountDownLatch

Based on Redisson Redisson distributed atresia (CountDownLatch) Java objects RCountDownLatch adopted with Java. Util. Concurrent. CountDownLatch similar interface and usage.

RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(1);
latch.await();

// In other threads or other JVMS
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.countDown();
Copy the code