1, Foreword
Redisson distributed interlocking RedissonMultiLock objects based on Redis can associate multiple RLock objects into an interlocking, and each RLock object instance can come from different redisson instances. Of course, this is the introduction of the official website. What is it? Let's take a look at the use and source code of interlock MultiLock!
2, MultiLock use
According to the official documents, the Redisson client here can not be the same. Of course, it is not necessary to use a client in general work.
3, Lock
Source entry: org redisson. Redissonmultilock #lock(), the default timeout leaseTime is not set, so it is - 1.
public void lock(long leaseTime, TimeUnit unit) { try { lockInterruptibly(leaseTime, unit); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }
This method is too long. Let's split it up and read it.
- Basic waiting time baseWaitTime = number of locks * 1500, which is 4500 milliseconds here;
- leaseTime == -1, so waitTime = baseWaitTime, that is 4500;
- while (true) call tryLock to lock until it succeeds.
Call the tryLock method, where the parameters waitTime = 4500, leaseTime = -1, unit = MILLISECONDS.
Let's take a look at the logic in tryLock?
leaseTime != - If 1 is not satisfied, skip this part directly.
waitTime != -1. If the conditions are met, remainTime = 4500, lockWaitTime = 4500.
Therefore, the failedLocksLimit() method directly returns 0, which means that all locks must be successful.
Here is the point: traverse all locks and lock them in turn. Locking logic is no different from reentrant locking. So Lua script will not be analyzed.
The above is the result of tryLock locking.
If the lock is successfully added, the successful lock will be put into the acquiredLocks set; If locking fails, you need to judge the failedLocksLimit. Because this is 0, it will directly release all locks in the successfully locked collection acquiredLocks, empty the successful collection and recover the iterator.
After each lock is added, the remaining time remainTime of the lock will be updated. If remainTime is less than or equal to 0, it indicates that the lock has timed out and returns false directly. This will execute the external while (true) logic, and then go through RedissonMultiLock#tryLock again.
- summary
According to the understanding, the drawing is as follows: in general, it is to put key1, key2, key3... keyN into a List set, and then iterate and cycle locking until all are successful.
- The difference between lock and tryLock
- tryLock() indicates that it is used to attempt to acquire the lock. If the acquisition is successful, it returns true. If the acquisition fails (that is, the lock has been acquired by other threads), it returns false
- The tryLock(long time, TimeUnit unit) method is similar to the tryLock() method, except that this method will wait for a certain time when the lock cannot be obtained. If the lock cannot be obtained within the time limit, it will return false. Returns true if the lock is obtained at the beginning or during the waiting period
- Trylock (long waittime, long leaseTime, timeunit) is based on 2. If a lock is obtained, the maximum holding time of the lock is leaseTime
4, Lock release
After reading the lock logic, the lock release is easier to understand.
You can directly traverse and release the lock Unlockasync() is the called RedissonBaseLock#unlockAsync() method.
5, Implementing distributed locks using MultiLock
-
Establish a three master and three slave redis cluster, Reference articles
-
Create a springboot project
-
redissonCluster.yml
clusterServersConfig: # Connection idle timeout, unit: ms, default: 10000 idleConnectionTimeout: 10000 pingConnectionInterval: 1000 # The wait timeout when establishing a connection with any node. The time unit is milliseconds. The default is 10000 connectTimeout: 10000 # Time to wait for the node to reply to the command. The time starts when the command is sent successfully. Default 3000 timeout: 3000 # Command failure retries retryAttempts: 3 # Command retry sending interval in milliseconds retryInterval: 1500 # Reconnect interval in milliseconds failedSlaveReconnectionInterval: 3000 # Maximum number of execution failures #failedAttempts: 3 # password password: # Maximum subscriptions per connection subscriptionsPerConnection: 5 clientName: null # Selection of loadBalancer load balancing algorithm class loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {} # The minimum number of idle connections of the primary node is 32 by default masterConnectionMinimumIdleSize: 32 # The primary node connection pool size is 64 by default masterConnectionPoolSize: 64 # Load balancing mode of subscription operation subscriptionMode: SLAVE # Read from server only readMode: SLAVE # Cluster address nodeAddresses: - "redis://xxx.xxx.xxx.xxx:9001" - "redis://xxx.xxx.xxx.xxx:9002" - "redis://xxx.xxx.xxx.xxx:9003" # The time interval for status scanning of Redis cluster nodes. The unit is milliseconds. Default 1000 scanInterval: 1000 #This number of thread pools is shared by all RTopic object listeners, RRemoteService callers, and RExecutorService tasks. Default 2 threads: 0 #The number of thread pools is the number of threads saved in the thread pool shared by all distributed data types and services created by a Redisson instance and the underlying clients. Default 2 nettyThreads: 0 # The default encoding method is org redisson. codec. JsonJacksonCodec codec: !<org.redisson.codec.JsonJacksonCodec> {} #transmission mode transportMode: NIO # The automatic expiration time of the distributed lock prevents deadlock. The unit is milliseconds. The default is 30s. Every 1 / 3 of the lockWatchdogTimeout time. If the game business is not executed, the lock will be automatically renewed lockWatchdogTimeout: 30000 # This parameter is used to modify whether messages are sent out according to the receiving order of subscribed and published messages. If no is selected, parallel processing will be implemented for messages. This parameter is only applicable to subscribed and published messages. The default is true keepPubSubOrder: true # Used to specify the behavior of high-performance engines. Since the selection of this variable value is closely related to the use scenario (except NORMAL), we recommend trying each parameter value. # #This parameter is limited to Redisson PRO version only. #performanceMode: HIGHER_THROUGHPUT
@Configuration public class RedissonHttpSessionConfig { //Call shutdown after service is disabled @Bean(destroyMethod="shutdown") public RedissonClient getRedissonClient() throws IOException { ResourceLoader loader = new DefaultResourceLoader(); Resource resource = loader.getResource("redissonCluster.yml"); Config config = Config.fromYAML(resource.getInputStream()); return Redisson.create(config); } }
@Component public class RedissonMultiLockInit { private final ArrayList<RLock> rLockList=new ArrayList<>(); @Autowired RedissonClient redissonClient; public RedissonMultiLock initLock(String... locksName){ for (String lockName : locksName) { rLockList.add(redissonClient.getLock(lockName)); } RLock[] rLocks = rLockList.toArray(new RLock[0]); return new RedissonMultiLock(rLocks); } public List<RLock> getRLocks(){ return rLockList; } }
@Controller @RequestMapping("/lock") public class LockController { @Autowired RedissonMultiLockInit redissonMultiLockInit; @Autowired UserMapper userMapper; @Autowired PlatformTransactionManager transactionManager; @GetMapping("/get/{waitTime}/{leaseTime}") @ResponseBody public String getLock(@PathVariable long waitTime, @PathVariable long leaseTime) throws InterruptedException { String[] strings={"test1","test2","test3"}; RedissonMultiLock lock = redissonMultiLockInit.initLock(strings); //When transaction management is enabled manually, @ Transitional cannot control the distributed locks of redis //Create transaction definition object DefaultTransactionDefinition def = new DefaultTransactionDefinition(); //Set whether to read only. false supports transactions def.setReadOnly(false); //Set the transaction isolation level. You can repeatedly read the default level of mysql def.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ); //Set transaction propagation behavior def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); //Configure transaction manager TransactionStatus status = transactionManager.getTransaction(def); if (lock.tryLock(waitTime,leaseTime, TimeUnit.SECONDS)){ System.out.println(Thread.currentThread().getName()+" waiting time is "+waitTime+"s " + "leaseTime is "+leaseTime+"s "+ "execute time is "+(leaseTime+10)+" s" ); try { userMapper.updateById(new User(1L,23,"beijing","myname2")); //Simulate execution timeout release lock Thread.sleep((leaseTime+10)*1000); List<RLock> rLocks = redissonMultiLockInit.getRLocks(); //Determine whether all locks are still held to prevent lock expiration if(rLocks.stream().allMatch(RLock::isLocked)){ //Submit business transactionManager.commit(status); //Release the distributed lock after submitting the business lock.unlock(); return "unlock success,transition success"; } else { //Rollback business transactionManager.rollback(status); return "lock is expired,transition fail"; } } catch (Exception e) { e.printStackTrace(); return "transition error"; } } else { return Thread.currentThread().getName()+" can't get the lock,because the waiting time isn't enough. Waiting time is "+waitTime+"s, " + "leaseTime is "+leaseTime+"s "; } } }
- Note: there is a classic problem that @ Transitional and distributed locks are used at the same time. To solve this problem, we manually start the transaction and ensure that the distributed lock is released after the transaction is committed. About this problem, You can refer to this article
- test
-
http://localhost:8090/lock/get/6/-1. It means that there is a maximum of 6s waiting time to obtain the lock, and the execution time of the business can be non renewed (enable the watchdog mechanism), then the business will be successful this time
-
http://localhost:8090/lock/get/6/9 , which means that there is a maximum of 6s waiting time to obtain the lock, and a maximum of 9s business execution time. If it times out, the distributed lock will be released. The business fails. Because I wrote the business in the controller for more than 9s, the business must fail this time.
-
http://localhost:8090/lock/get/6/-1 and http://localhost:8090/lock/get/2/-1
Since the first service takes 9s of service execution time, the second service cannot obtain the distributed lock within 2s and will exit the service.