preface

Hello everyone, I am L la not rami, today to share with you redisson implementation of multi-type lock, support almost all lock scenarios redis distributed lock implementation, but also support small MQ and REDis various data operations, complete source code can add my private chat.

This article has been included in Github- Java3C, which contains my series of articles, welcome everyone Star.

Theoretical part

In the previous article Redis distributed lock, we introduced two ways to implement distributed lock through Redis.

  1. Through the redis command: setNX
  2. Through redis client: Redisson

Redisson supports more lock types, such as interlock, red lock, read/write lock, fair lock, etc. Redisson’s implementation is simpler, developers only need to call the responsive API, and do not need to worry about the underlying locking process and unlock atomicity issues.

In Redis distributed locks, Redisson’s simple, programmatic implementation of many lock types is listed. Such an implementation perfectly meets our daily development needs, but the downsides are obvious.

Such as:

  • The code is too embedded to be elegant
  • Duplicate code
  • The use of lock parameters is not intuitive
  • Easy to forget the unlock step

Those of you who have used Spring know the @Transactional annotation. Spring supports both programmatic and declarative transactions.

Can we also refer to such an implementation?

The answer is: totally OK!

That’s what AOP is all about.

Actual part

1. Introduce redisson dependencies

< the dependency > < groupId > org. Redisson < / groupId > < artifactId > redisson < / artifactId > < version > 3.16.2 < / version > </dependency>Copy to clipboardErrorCopiedCopy the code

2. Custom annotations

/** * Distributed lock custom annotation */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lock {

    /** * Lock mode: If automatic mode is not set, when only one parameter. Use MULTIPLE */ with the REENTRANT parameter
    LockModel lockModel(a) default LockModel.AUTO;

    /** * If there are more than one keys, if not set, use interlock **@return* /
    String[] keys() default {};

    /** * static constant for key: when the spEL of key is a LIST or array, the + sign concatenation will be treated as a string by spEL, which will only generate a lock and not achieve our purpose. * If we need a constant. This parameter will be concatenated at the end of each element@return* /
    String keyConstant(a) default "";

    /** * Lock timeout duration, default 30000 ms (configurable globally in configuration file) **@return* /
    long watchDogTimeout(a) default 30000;

    /** * Wait lock timeout. Default value: 10000 ms -1 Indicates that you have been waiting (configurable globally) **@return* /
    long attemptTimeout(a) default 10000;
}
Copy the code

Constant class

/** * Redisson constant class */
public class RedissonConst {
    /** * redisson lock default prefix */
    public static final String REDISSON_LOCK = "redisson:lock:";
    /** * spEL expression placeholder */
    public static final String PLACE_HOLDER = "#";
}
Copy the code

4, the enumeration

/** * Lock mode */
public enum LockModel {
    /**
     * 可重入锁
     */
    REENTRANT,
    /** * fair lock */
    FAIR,
    /** * interlock */
    MULTIPLE,
    /** * redlock */
    RED_LOCK,
    /** * read lock */
    READ,
    /** * write lock */
    WRITE,
    /** * Automatic mode, when only one parameter uses REENTRANT parameter multiple RED_LOCK */
    AUTO
}
Copy the code

5. Custom exceptions

/** * Distributed lock exception */
public class ReddissonException extends RuntimeException {

    public ReddissonException(a) {}public ReddissonException(String message) {
        super(message);
    }

    public ReddissonException(String message, Throwable cause) {
        super(message, cause);
    }

    public ReddissonException(Throwable cause) {
        super(cause);
    }

    public ReddissonException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace); }}Copy the code

6. AOP cutting

   /** * distributed lock AOP */
@Slf4j
@Aspect
public class LockAop {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RedissonProperties redissonProperties;

    @Autowired
    private LockStrategyFactory lockStrategyFactory;

    @Around("@annotation(lock)")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint, Lock lock) throws Throwable {
        // An array of keys to lock
        String[] keys = lock.keys();
        if (ArrayUtil.isEmpty(keys)) {
            throw new ReddissonException(Redisson lock keys cannot be empty);
        }
        // Get the method parameter name
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) proceedingJoinPoint.getSignature()).getMethod());
        Object[] args = proceedingJoinPoint.getArgs();
        // Timeout time to wait for lock
        long attemptTimeout = lock.attemptTimeout();
        if (attemptTimeout == 0) {
            attemptTimeout = redissonProperties.getAttemptTimeout();
        }
        // Lock timeout time
        long lockWatchdogTimeout = lock.watchdogTimeout();
        if (lockWatchdogTimeout == 0) {
            lockWatchdogTimeout = redissonProperties.getLockWatchdogTimeout();
        }
        // Lock mode
        LockModel lockModel = getLockModel(lock, keys);
        if(! lockModel.equals(LockModel.MULTIPLE) && ! lockModel.equals(LockModel.RED_LOCK) && keys.length >1) {
            throw new ReddissonException("Multiple parameters, lock mode is ->" + lockModel.name() + ", cannot match lock");
        }
        log.info("Lock mode ->{}, lock time ->{} ms, lock maximum time ->{} ms", lockModel.name(), attemptTimeout, lockWatchdogTimeout);
        boolean res = false;
        // Policy mode gets the Redisson lock object
        RLock rLock = lockStrategyFactory.createLock(lockModel, keys, parameterNames, args, lock.keyConstant(), redissonClient);
        / / implementation of aop
        if(rLock ! =null) {
            try {
                if (attemptTimeout == -1) {
                    res = true;
                    // Wait for the lock
                    rLock.lock(lockWatchdogTimeout, TimeUnit.MILLISECONDS);
                } else {
                    res = rLock.tryLock(attemptTimeout, lockWatchdogTimeout, TimeUnit.MILLISECONDS);
                }
                if (res) {
                    return proceedingJoinPoint.proceed();
                } else {
                    throw new ReddissonException("Lock acquisition failed"); }}finally {
                if(res) { rLock.unlock(); }}}throw new ReddissonException("Lock acquisition failed");
    }

    /** * get lock mode **@param lock
     * @param keys
     * @return* /
    private LockModel getLockModel(Lock lock, String[] keys) {
        LockModel lockModel = lock.lockModel();
        // Automatic mode: matches the global configuration first, then determines whether to use red lock or reentrant lock
        if (lockModel.equals(LockModel.AUTO)) {
            LockModel globalLockModel = redissonProperties.getLockModel();
            if(globalLockModel ! =null) {
                lockModel = globalLockModel;
            } else if (keys.length > 1) {
                lockModel = LockModel.RED_LOCK;
            } else{ lockModel = LockModel.REENTRANT; }}returnlockModel; }}Copy the code

Policy patterns are used to provide implementations for different lock types.

7. Implementation of lock strategy

Define the abstract base class (or interface) for the lock policy:

/** * Lock policy abstract base class */
@Slf4j
abstract class LockStrategy {

    @Autowired
    private RedissonClient redissonClient;

    /** * create RLock **@param keys
     * @param parameterNames
     * @param args
     * @param keyConstant
     * @return* /
    abstract RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient);

    /** * get RLock **@param keys
     * @param parameterNames
     * @param args
     * @param keyConstant
     * @return* /
    public RLock[] getRLocks(String[] keys, String[] parameterNames, Object[] args, String keyConstant) {
        List<RLock> rLocks = new ArrayList<>();
        for (String key : keys) {
            List<String> valueBySpel = getValueBySpel(key, parameterNames, args, keyConstant);
            for (String s : valueBySpel) {
                rLocks.add(redissonClient.getLock(s));
            }
        }
        RLock[] locks = new RLock[rLocks.size()];
        int index = 0;
        for (RLock r : rLocks) {
            locks[index++] = r;
        }
        return locks;
    }

    /** * Get arguments ** from spring Spel@paramKey Defines a key value that starts with # for example :#user *@paramParameterNames parameters *@paramArgs parameter value *@paramKeyConstant key is steady *@return* /
    List<String> getValueBySpel(String key, String[] parameterNames, Object[] args, String keyConstant) {
        List<String> keys = new ArrayList<>();
        if(! key.contains(PLACE_HOLDER)) { String s = REDISSON_LOCK + key + keyConstant; log.info("Spel expression value->{}", s);
            keys.add(s);
            return keys;
        }
        // SpEL parser
        ExpressionParser parser = new SpelExpressionParser();
        // spel context
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }
        Expression expression = parser.parseExpression(key);
        Object value = expression.getValue(context);
        if(value ! =null) {
            if (value instanceof List) {
                List valueList = (List) value;
                for(Object o : valueList) { keys.add(REDISSON_LOCK + o.toString() + keyConstant); }}else if (value.getClass().isArray()) {
                Object[] objects = (Object[]) value;
                for(Object o : objects) { keys.add(REDISSON_LOCK + o.toString() + keyConstant); }}else {
                keys.add(REDISSON_LOCK + value.toString() + keyConstant);
            }
        }
        log.info(Spel expression key={},value={}, key, keys);
        returnkeys; }}Copy the code

To provide a variety of lock mode concrete implementation:

  • Reentrant lock:
/** * Reentrant lock policy */
public class ReentrantLockStrategy extends LockStrategy {

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        List<String> valueBySpel = getValueBySpel(keys[0], parameterNames, args, keyConstant);
        // Use a red lock if the spEL expression is an array or collection
        if (valueBySpel.size() == 1) {
            return redissonClient.getLock(valueBySpel.get(0));
        } else {
            RLock[] locks = new RLock[valueBySpel.size()];
            int index = 0;
            for (String s : valueBySpel) {
                locks[index++] = redissonClient.getLock(s);
            }
            return newRedissonRedLock(locks); }}}Copy the code
  • Fair lock:
/** * Fair lock policy */
public class FairLockStrategy extends LockStrategy {

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        return redissonClient.getFairLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0)); }}Copy the code
  • interlocking
/** * interlock policy */
public class MultipleLockStrategy extends LockStrategy {

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        RLock[] locks = getRLocks(keys, parameterNames, args, keyConstant);
        return newRedissonMultiLock(locks); }}Copy the code
  • Red lock
/** * redlock policy */
public class RedLockStrategy extends LockStrategy {

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        RLock[] locks = getRLocks(keys, parameterNames, args, keyConstant);
        return newRedissonRedLock(locks); }}Copy the code
  • Read lock
/** * Read lock policy */
public class ReadLockStrategy extends LockStrategy {

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        RReadWriteLock rwLock = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0));
        returnrwLock.readLock(); }}Copy the code
  • Write lock
/** * Write lock policy */
public class WriteLockStrategy extends LockStrategy {

    @Override
    public RLock createLock(String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        RReadWriteLock rwLock = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, keyConstant).get(0));
        returnrwLock.writeLock(); }}Copy the code

Finally, a policy factory is provided to initialize the lock policy:

/** * Lock policy factory */
@Service
public class LockStrategyFactory {

    private LockStrategyFactory(a) {}private static final Map<LockModel, LockStrategy> STRATEGIES = new HashMap<>(6);

    static {
        STRATEGIES.put(LockModel.FAIR, new FairLockStrategy());
        STRATEGIES.put(LockModel.REENTRANT, new ReentrantLockStrategy());
        STRATEGIES.put(LockModel.RED_LOCK, new RedLockStrategy());
        STRATEGIES.put(LockModel.READ, new ReadLockStrategy());
        STRATEGIES.put(LockModel.WRITE, new WriteLockStrategy());
        STRATEGIES.put(LockModel.MULTIPLE, new MultipleLockStrategy());
    }

    public RLock createLock(LockModel lockModel, String[] keys, String[] parameterNames, Object[] args, String keyConstant, RedissonClient redissonClient) {
        returnSTRATEGIES.get(lockModel).createLock(keys, parameterNames, args, keyConstant, redissonClient); }}Copy the code

8. Way of use

    @Lock(keys = "#query.channel") / / support spel
    @apiOperation (" Paging list ")
    @GetMapping
    public ApiPageResult list(VendorProjectItemQuery query, Pagination pagination) {
        return ApiPageResult.success(pagination, vendorProjectItemService.list(query, pagination), vendorProjectItemService.count(query));
    }
Copy the code

All done, smooth chicken