Detailed Explanation of Distributed Lock Based on redis

  • 2021-09-11 20:20:13
  • OfStack

Analysis of the principle of directory preface, implementation of writing annotations, interceptor intercepting the above mentioned tools

Preface

In order to ensure that one can only be operated by the same thread in a high concurrent memory scenario, java concurrent processing provides ReentrantLock or Synchronized for mutual exclusion control. But this is only valid for stand-alone environments. We implement distributed locking in about three ways.

redis implements distributed locking Distributed locking in database zk implements distributed locking

Principle analysis

The above three kinds of distributed locks all control release or rejection by locking and unlocking each request according to each other. The redis lock is based on the setnx command it provides.

setnx if and only if key does not exist. If a given key already exists, setnx does not do anything. setnx is an atomic operation.

Compared with distributed database, redis has lightweight memory. Therefore, redis distributed locking performance is better

Realization

The principle is simple. Combined with the springboot project, we realized a set of cases to understand the inventory locking of the interface in the form of annotations

Write annotations

We write annotations. It is convenient for us to add annotations on the interface to provide interception information


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface StockLock {

    /**
     * 
     * @Description  Lock key Prefix of 
     * @Date 15:25 2020 Year 03 Month 25 Day , 0025
     * @Param []
     * @return java.lang.String
     */
    String prefix() default "";
    /**
     *
     * @Description key Delimiter of 
     * @Date 15:27 2020 Year 03 Month 25 Day , 0025
     * @Param []
     * @return java.lang.String
     */
    String delimiter() default ":";
}

@Target({ElementType.PARAMETER , ElementType.METHOD , ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface StockParam {
    /*
    * @Description  Composition key
    * @Date 11:11 2020 Year 03 Month 26 Day , 0026
    * @Param []
    * @return java.lang.String[]
    */
    String[] names() default {""};
}

Interceptor interception

The key to the implementation of redis distributed lock is the writing of interceptor. The above annotation is only an aid to interception.


@Around("execution(public * *(..)) && @annotation(com.ay.framework.order.redis.product.StockLock)")

Interception of StockLock annotations through Around of springboot. Through interception, we can get the interception method, parameters, and required lock parameters.

We get the name of the required lock here called "a" and then decrement the key through the atomicity operation of redis.

In order to facilitate us to update the inventory when reducing the inventory. We need another lock before decreasing the inventory. This lock is called "a_key"

In other words, if our interface wants to access, it must acquire the "a" lock, and obtaining the "a" lock requires reducing inventory. You need to acquire the "a_key" lock before reducing inventory.

After getting the lock and processing the logic, we need to release the corresponding lock.


RedisAtomicLong entityIdCounter = new RedisAtomicLong(lockKey, redisTemplate.getConnectionFactory());
    if (redisTemplate.hasKey(CoreConstants.UPDATEPRODUCTREDISLOCKKEY + lockKey)) {
        // Denote lockKey There is a change in the inventory information of. You cannot trade at this time 
        throw new BusinessException(" Inventory changes. Temporarily unable to trade ");
    }
    Long increment = entityIdCounter.decrementAndGet();
    if (increment >= 0) {
        try {
            Object proceed = pjp.proceed();
        } catch (Throwable throwable) {
            // The occupied resources need to be released back into the resource pool 
            while (!redisLock.tryGetLock(CoreConstants.UPDATEPRODUCTREDISLOCKKEY + lockKey, "")) {

            }
            // Denote lockKey There is a change in the inventory information of. You cannot trade at this time 
            long l = entityIdCounter.incrementAndGet();
            if (l < 1) {
                redisTemplate.opsForValue().set(lockKey,1);
            }
            redisLock.unLock(CoreConstants.UPDATEPRODUCTREDISLOCKKEY + lockKey);
            throwable.printStackTrace();
        }
    } else {
        redisTemplate.opsForValue().set(lockKey,0);
        throw new BusinessException(" Insufficient stock! Unable to operate ");
    }

Because we need to release the lock when we lock it. However, when the program handles the business in the middle, an exception occurs, which leads to the failure to release the lock. At this time, our distributed lock 1 is directly locked. Commonly known as "deadlock". In order to avoid this kind of scene. We often give a valid period when locking. Automatic release lock after expiration date. This feature coincides with the expiration strategy of redis.

Tools mentioned above

RedisLock


public Boolean tryGetLock(String key , String value) {
    return tryGetLock(key, value, -1, TimeUnit.DAYS);
}
public Boolean tryGetLock(String key , String value, Integer expire) {
    return tryGetLock(key, value, expire, TimeUnit.SECONDS);
}
public Boolean tryGetLock(String key , String value, Integer expire , TimeUnit timeUnit) {
    ValueOperations operations = redisTemplate.opsForValue();
    if (operations.setIfAbsent(key, value)) {
        // Description  redis There is no such thing as key ,  In other words   Lock succeeded    Set expiration time to prevent deadlock 
        if (expire > 0) {
            redisTemplate.expire(key, expire, timeUnit);
        }
        return true;
    }
    return false;
}

public Boolean unLock(String key) {
    return redisTemplate.delete(key);
}

StockKeyGenerator


@Component()
@Primary
public class StockKeyGenerator implements CacheKeyGenerator {
    @Override
    public String getLockKey(ProceedingJoinPoint pjp) {
        // Get method signature 
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        // Acquisition method cacheLock Annotation 
        StockLock stockLock = method.getAnnotation(StockLock.class);
        // Get method parameters 
        Object[] args = pjp.getArgs();
        Parameter[] parameters = method.getParameters();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < parameters.length; i++) {
            StockParam stockParam = parameters[i].getAnnotation(StockParam.class);
            Object arg = args[i];
            if (arg instanceof Map) {
                Map<String, Object> temArgMap = (Map<String, Object>) arg;
                String[] names = stockParam.names();
                for (String name : names) {
                    if (builder.length() > 0) {
                        builder.append(stockLock.delimiter());
                    }
                    builder.append(temArgMap.get(name));
                }
            }

        }
        return builder.toString();
    }
}

Problem analysis

The above analysis of a deadlock scenario, theoretically out of deadlock we redis distributed lock a good solution to the distributed problem. But there will still be problems. The following lists the problems encountered in writing this site.

Business processing time > Lock expiration time

The a thread acquires the lock, and it takes 8S to start business processing Within 8S, the lock is valid for 5S. After the lock expires, that is, the 6S, b can acquire a new lock when the b thread enters and begins to acquire the lock. There is something wrong at this time. Assuming that the b thread only needs 3S for business processing, but because the a thread released the lock, although the b thread did not release the lock at the 8S, the lock of b did not expire, but there was no lock at this time. So that C threads can also enter

The above is a detailed explanation of the implementation of distributed locks based on redis in detail, more information about the implementation of distributed locks based on redis please pay attention to other related articles on this site!


Related articles: