What is a distributed lock?
In a JVM, where multiple threads are concurrent, we can use synchronous or Lock locks to ensure that only one thread can modify shared variables or execute code blocks at a time.However, our services are deployed on a distributed cluster basis, and for some shared resources, the use of Java locks in a distributed environment has no effect.
Implementing a distributed lock using a database is easy to understand, just based on the database, and no third-party middleware is needed, so this is the preferred way for many distributed businesses to implement distributed locks.However, the distributed locks implemented by the database have performance bottlenecks to some extent, so I recommend Redis.
Redis Implements Distributed Locks
Redis implements distributed locks using a combination of SETNX and EXPIRE, with the following implementation code prior to Redis version 2.6.12:
public static boolean tryLock(Jedis jedis, String lockKey, String requestId, int expireTime) { Long result = jedis.setnx(lockKey, requestId);//Set Lock if (result == 1) {//Lock acquisition succeeded // If the program crashes suddenly here, the expiration time cannot be set and a deadlock will occur jedis.expire(lockKey, expireTime);//Delete locks by expiration time return true; } return false; }
Distributed locks implemented in this way are set by the setnx() method and return fails if lockKey exists or otherwise returns success.After successful setup, in order to successfully release the lock after completing the synchronization code, the method also needs to use expire() method to set an expiration time for the lockKey value, confirm that the key value is deleted, and avoid lock unrelease, which prevents the next thread from acquiring the lock, i.e., deadlock problem.
If a program crashes before and after setting an expiration time, a deadlock problem will occur if lockKey does not set an expiration time.
SETNX added an expiration time parameter after Redis version 2.6.12:
private static final String LOCK_SUCCESS = "OK"; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; /** * Attempting to acquire a distributed lock * @param jedis Redis Client * @param lockKey lock * @param requestId Request Identification * @param expireTime Overdue time * @return Success or not */ public static boolean tryLock(Jedis jedis, String lockKey, String requestId, int expireTime) { String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; }
We can also use Lua scripts to set locks and atomize expiration timesJedis.eval() Method runs the script:
// Locking script private static final String SCRIPT_LOCK = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then redis.call('pexpire', KEYS[1], ARGV[2]) return 1 else return 0 end"; // Unlock script private static final String SCRIPT_UNLOCK = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Although the SETNX method guarantees the atomicity of setting locks and expiration times, if we set the expiration time to be shorter and the execution time to be longer, there will be problems with the failure of lock code blocks.We need to set the expiration time long enough to ensure that these problems do not occur.
This scheme is currently the best distributed locking scheme, but if it is in a Redis cluster environment, there are still problems.Since Redis cluster data is asynchronous when synchronized to each node, if the Master node crashes when it acquires a lock and does not synchronize to another node, the new Master node can still acquire the lock, so multiple application services can acquire the lock at the same time.
Redlock algorithm
Redisson was officially launched by Redis.It not only provides a series of distributed Java common objects, but also provides many distributed services.Redisson is based on the netty communication framework, so it supports non-blocking communication with better performance than Jedis, which is familiar to us.
Redis distributed locks are implemented in Redisson and support single-point and cluster modes.In cluster mode, Redisson uses the Redlock algorithm to avoid multiple applications acquiring locks simultaneously when the Master node crashes and switches to another Master.We can learn about the implementation of the Redlock algorithm by using an application service to obtain distributed locks:
The code implementation is as follows:
-
First introduce the jar package:
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.8.2</version> </dependency>
-
Configuration file for implementing Redisson:
@Bean public RedissonClient redissonClient() { Config config = new Config(); config.useClusterServers() .setScanInterval(2000) // Cluster state scan interval in milliseconds .addNodeAddress("redis://127.0.0.1:7000).setPassword("1") .addNodeAddress("redis://127.0.0.1:7001").setPassword("1") .addNodeAddress("redis://127.0.0.1:7002") .setPassword("1"); return Redisson.create(config); }
- Acquire lock operation:
long waitTimeout = 10; long leaseTime = 1; RLock lock1 = redissonClient1.getLock("lock1"); RLock lock2 = redissonClient2.getLock("lock2"); RLock lock3 = redissonClient3.getLock("lock3"); RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3); // Simultaneous Locking: lock1 lock2 lock3 // Red locks successfully lock on most nodes, even if successful, with total and individual node timeouts set redLock.trylock(waitTimeout,leaseTime,TimeUnit.SECONDS); ... redLock.unlock();