Redis distributed lock optimization with SpringBoot Redisson set up Java in-memory Data Grid based on Redis, based on NIO Netty framework, using Redis key database. It is very powerful and solves many problems in distributed architectures.

Github wiki: github.com/redisson/re…

Official documentation: github.com/redisson/re…

Project code structure diagram:

Import dependence

<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.8.0</version>Copy the code

The configuration properties

Add a single machine & sentry configuration to the application.properites resource file

Server. port=3000 # redisson.address=redis://127.0.0.1:6379 redisson.password= # sentry mode #redisson.master-name= master #redisson.password= # redisson. Sentinel - addresses = 10.47.91.83:26379,10.47. 91.83:26380,10.47. 91.83:26381Copy the code

Note:

A URI build error is reported without the redis:// prefix

Caused by: java.net.URISyntaxException: Illegal character in scheme name at index 0
Copy the code

More configuration information can be found on the official website

The interface definition class that defines Lock

package com.tuhu.thirdsample.service; import org.redisson.api.RLock; import java.util.concurrent.TimeUnit; /** * @author chendesheng * @create 2019/10/12 10:48 */ public interface DistributedLocker { RLock lock(String lockKey);  RLock lock(String lockKey, int timeout); RLock lock(String lockKey, TimeUnit unit, int timeout); boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime); void unlock(String lockKey); void unlock(RLock lock); }Copy the code

Lock interface implementation class

package com.tuhu.thirdsample.service; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import java.util.concurrent.TimeUnit; /** * @author chendesheng * @create 2019/10/12 10:49 */ public class RedissonDistributedLocker implements DistributedLocker{ private RedissonClient redissonClient; @Override public RLock lock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.lock(); return lock; } @Override public RLock lock(String lockKey, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); lock.lock(leaseTime, TimeUnit.SECONDS); return lock; } @Override public RLock lock(String lockKey, TimeUnit unit ,int timeout) { RLock lock = redissonClient.getLock(lockKey); lock.lock(timeout, unit); return lock; } @Override public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) { RLock lock = redissonClient.getLock(lockKey); try { return lock.tryLock(waitTime, leaseTime, unit); } catch (InterruptedException e) { return false; } } @Override public void unlock(String lockKey) { RLock lock = redissonClient.getLock(lockKey); lock.unlock(); } @Override public void unlock(RLock lock) { lock.unlock(); } public void setRedissonClient(RedissonClient redissonClient) { this.redissonClient = redissonClient; }}Copy the code

The Redisson property assembles the class

package com.tuhu.thirdsample.common;

import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author chendesheng
 * @create 2019/10/11 20:04
 */
@Configuration
@ConfigurationProperties(prefix = "redisson")
@ConditionalOnProperty("redisson.password")
@Data
public class RedissonProperties {


    private int timeout = 3000;

    private String address;

    private String password;

    private int database = 0;

    private int connectionPoolSize = 64;

    private int connectionMinimumIdleSize=10;

    private int slaveConnectionPoolSize = 250;

    private int masterConnectionPoolSize = 250;

    private String[] sentinelAddresses;

    private String masterName;

}
Copy the code

SpringBoot auto-assembly class

package com.tuhu.thirdsample.configuration; import com.tuhu.thirdsample.common.RedissonProperties; import com.tuhu.thirdsample.service.DistributedLocker; import com.tuhu.thirdsample.service.RedissonDistributedLocker; import com.tuhu.thirdsample.util.RedissonLockUtil; import org.apache.commons.lang3.StringUtils; import org.redisson.Redisson; import org.redisson.api.RedissonClient; import org.redisson.config.Config; import org.redisson.config.SentinelServersConfig; import org.redisson.config.SingleServerConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author chendesheng * @create 2019/10/12 10:50 */ @Configuration @ConditionalOnClass(Config.class) @EnableConfigurationProperties(RedissonProperties.class) public class RedissonAutoConfiguration { @Autowired private RedissonProperties redissonProperties; /** * ConditionalOnProperty(name="redisson.master-name") RedissonClient redissonSentinel() { Config config = new Config(); SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redissonProperties.getSentinelAddresses()) .setMasterName(redissonProperties.getMasterName()) .setTimeout(redissonProperties.getTimeout()) .setMasterConnectionPoolSize(redissonProperties.getMasterConnectionPoolSize()) .setSlaveConnectionPoolSize(redissonProperties.getSlaveConnectionPoolSize()); if(StringUtils.isNotBlank(redissonProperties.getPassword())) { serverConfig.setPassword(redissonProperties.getPassword()); } return Redisson.create(config); } @bean@conditionalonProperty (name="redisson.address") RedissonClient redissonSingle() { Config config = new Config(); SingleServerConfig serverConfig = config.useSingleServer() .setAddress(redissonProperties.getAddress()) .setTimeout(redissonProperties.getTimeout()) .setConnectionPoolSize(redissonProperties.getConnectionPoolSize()) .setConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumIdleSize()); if(StringUtils.isNotBlank(redissonProperties.getPassword())) { serverConfig.setPassword(redissonProperties.getPassword()); } return Redisson.create(config); } /** * Install the locker class, And inject the instance into RedissLockUtil * @return */ @bean DistributedLocker DistributedLocker (RedissonClient RedissonClient) { DistributedLocker locker = new RedissonDistributedLocker(); ((RedissonDistributedLocker) locker).setRedissonClient(redissonClient); RedissonLockUtil.setLocker(locker); return locker; }}Copy the code

The Lock of the helper classes

package com.tuhu.thirdsample.util; import com.tuhu.thirdsample.service.DistributedLocker; import org.redisson.api.RLock; import java.util.concurrent.TimeUnit; /** * @author chendesheng * @create 2019/10/12 10:54 */ public class RedissonLockUtil { private static DistributedLocker  redissLock; public static void setLocker(DistributedLocker locker) { redissLock = locker; } /** * @param lockKey * @return */ public static RLock lock(String lockKey) {return redisslock. lock(lockKey); } @param lockKey */ public static void unlock(String lockKey) {redisslock.unlock (lockKey); } @param lock */ public static void unlock(RLock lock) {redissLock. Unlock (lock); } /** * @param lockKey * @param timeout Timeout unit: Seconds */ public static RLock lock(String lockKey, int timeout) {return redissLock. Lock (lockKey, timeout); } /** * @param lockKey * @param unit Time unit * @param timeout time unit */ public static RLock lock(String lockKey, String lockKey) TimeUnit unit , int timeout) { return redissLock.lock(lockKey, unit, timeout); } @param lockKey * @param waitTime maximum waitTime * @param leaseTime automatically release lock * @return */ public static Boolean  tryLock(String lockKey, int waitTime, int leaseTime) { return redissLock.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime); } /** ** attempt to obtain the lock * @param lockKey * @param unit time unit * @param waitTime maximum waitTime * @param leaseTime automatic release time after lock * @return */ public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) { return redissLock.tryLock(lockKey, unit, waitTime, leaseTime); }}Copy the code

Control layer

package com.tuhu.thirdsample.task; import com.tuhu.thirdsample.common.KeyConst; import lombok.extern.slf4j.Slf4j; import org.redisson.api.RLock; import org.redisson.api.RedissonClient; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.TimeUnit; /** * @author chendesheng * @create 2019/10/12 11:03 */ @RestController @RequestMapping("/lock") @Slf4j public class LockController { @Autowired RedissonClient redissonClient; @GetMapping("/task") public void task(){ log.info("task start"); RLock lock = redissonClient.getLock(KeyConst.REDIS_LOCK_KEY); boolean getLock = false; Try {if (getLock = lock.tryLock(0,5, timeunit.seconds)){// execute the service logic system.out.println (" get the lock "); }else {log.info("Redisson distributed lock did not acquire lock :{},ThreadName:{}", keycond.redis_lock_key, thread.currentThread ().getName()); }} catch (InterruptedException e) {log.error("Redisson caught a distributed lock exception :{}",e); }finally { if (! getLock){ return; } // You need to comment this code if you want to demonstrate it; //lock. Unlock (); //log.info("Redisson distributed lock release lock :{},ThreadName :{}", keyconsent.redis_lock_key, thread.currentThread ().getName()); }}}Copy the code

RLock inherited from Java. Util. Concurrent. The locks, Lock, it can be understood as a reentrant Lock, need to manually Lock and releases the Lock.

Look at one of its methods: tryLock(long waitTime, long leaseTime, TimeUnit unit)

GetLock = lock. TryLock (0, 5, TimeUnit. SECONDS)Copy the code

TryLock () will wait for waitTime if the lock is first obtained by another line. If the lock has not been obtained by another line, it will give up and return false. If a lock is acquired, it is held until the time specified by leaseTime is exceeded, unless unlock is called.

So that’s the core of Redisson’s approach to distributed locking. One might ask, how do you know that you’re using the same lock, and where is the distributed lock?

This is the power of Redisson. The underlying Redis is used for distributed locks. In our RedissonManager, we have specified Redis instances, which Redisson will host, similar to how we manually implement Redis distributed locks.

test

Start the program, enter the url in the postman: http://localhost:3000/lock/task

The first time we enter the result, we can see it on the console:

To request again within lock expiration time, we can see on the console:

The last

Ok, SpringBoot integration with Redisson implementation of distributed locking has been written to the end. Using Redisson makes everything more of an object, and we don’t need to know how to implement it internally, just how to use it. Of course, just knowing how to use is far from enough, familiar with understanding the underlying principle is more important!