I. Background introduction

With the increasing of high concurrency scenarios, the demand for distributed locks is increasing in all kinds of systems (especially those with high concurrency requirements such as seckill system).

In the code implementation of distributed lock, the following situations often exist

Public void method(){// Business code try{Boolean b = redisClient.trylock (key,value); if(b){ // ...... }catch(Exception e){log.error("error"); throw new Exception(); } the finally {the if (redisClient. Get (key) equals (value)) {redisClient. TryRelease (key); } // Business code}Copy the code

In many scenarios, this is how redisClient is implemented. You can choose how to implement redisClient.

The problem with this code is that if there are too many scenarios requiring distributed locks, there will be try catch finally blocks everywhere in the system, which will make the whole code look bloated and duplicate too much

So how do you solve the problem of making your code too bloated?

The answer is simple: use AOP techniques to slice the distributed locking out of the business code

How do you do that?

Two, distributed lock annotation + section of the simple implementation

First we implement a annotation, annotation naming suggestions should be intuitive, so that people can see the role of the annotation at a glance

@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface RedisDistributedLock {// The value of the locked resource, that is, the key stored in redis. String Key () default "default_key"; Int keepMills() default 1 * 10 * 1000; }Copy the code

The method of custom annotation is not described in this article. If you are interested, you can learn about it by yourself.

We want to implement distributed locks using the @redisdistributedLock annotation, as shown below.

@redisdistributedLock (key = "key_example", keepMills = 2*10*1000) public void method(){RedisDistributedLock(key = "key_example", keepMills = 2*10*1000)Copy the code

If key_example is not set, the default is default_key, and the lock is released for 20 seconds. If key_example is not set, the default is 10 seconds

So a simple implementation of a concrete AOP aspect is as follows

@Aspect @Component @Slf4j public class RedisDistributedLockAspect { @Autowired private RedisClient redisClient; @Around(value = "@annotation(redisDistributedLock)") public Object invoke(ProceedingJoinPoint point) throws Throwable { Signature signature = point.getSignature(); MethodSignature methodSignature = (MethodSignature)signature; Method targetMethod = methodSignature.getMethod(); / / get in the way that annotations RedisDistributedLock redisLock = targetMethod. GetAnnotation (RedisDistributedLock. Class); String key=redisLock.key(); if(StringUtils.isEmpty(key)){ key=point.getTarget().getClass().getName()+"."+targetMethod.getName(); } Boolean isLocked = redisClient.tryLock(key, "true", redisLock.keepMills()); if (! isLocked) { logger.debug("get lock failed : " + key); return null; } logger.debug("get lock success : " + key); try { return proceedingJoinPoint.proceed(); } catch (Exception e) { logger.error("execute locked method occured an exception", e); } finally { if (redisClient.get(key, String.class).equals(value)) { redisClient.tryRelease(key); } } return null; }}Copy the code

Dynamic method entry parameter as distributed lock key

The method() method can be locked in this way, but this is still a bit inconvenient

If I want to bemethod()What if the argument passed to the method is locked instead of just writing a key

As follows:

@redisdistributedLock (key = "param", keepMills = 2*10*1000) public void method(String param){Copy the code

This annotation would be much easier to use if we could dynamically lock the parameters passed to the method

Here we can use the Spel expression to process, about the Spel expression, please go to the query information, I only give the final code here

@Aspect @Component @Slf4j public class RedisDistributedLockAspect { @Autowired private RedisClient redisClient; private static Logger logger = LoggerFactory.getLogger(RedisDistributedLockAspect.class); @Around(value = "@annotation(redisDistributedLock)") public Object invoke(ProceedingJoinPoint proceedingJoinPoint, RedisDistributedLock redisDistributedLock) throws Throwable { String key = redisDistributedLock.key(); Object[] arg = proceedingJoinPoint.getArgs(); Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod(); / / get intercepted method parameter list (use the Spring support library) LocalVariableTableParameterNameDiscoverer localVariableTable = new LocalVariableTableParameterNameDiscoverer(); String[] parameterNames = localVariableTable.getParameterNames(method); // use SPEL to parse key ExpressionParser parser = new SpelExpressionParser(); / / SPEL context StandardEvaluationContext context = new StandardEvaluationContext (); For (int I = 0; i < parameterNames.length; i++) { context.setVariable(parameterNames[i], arg[i]); Matches ("^#.*.$")) {valueOfKey = parser. ParseExpression (key).getValue(context, String.class); String value = uuid.randomuuid ().tostring (); String value = uuid.randomuuid ().tostring (); Boolean isLocked = redisClient.tryLock(key, value, redisDistributedLock.keepMills()); if (! isLocked) { logger.debug("get lock failed : " + key); return null; } logger.debug("get lock success : " + key); try { return proceedingJoinPoint.proceed(); } catch (Exception e) { logger.error("execute locked method occured an exception", e); } finally { if (redisClient.get(key, String.class).equals(value)) { redisClient.tryRelease(key); } } return null; }}Copy the code

In this case, you can lock the parameters passed into the method by simply doing the following

@redisdistributedLock (key = "#param", keepMills = 2*10*1000) public void method(String param){Copy the code

\