How to use distributed locks in a distributed environment like the synchronized keyword. For example, develop an annotation called @distributionLock that applies to a method function and automatically releases the lock after each DistributionLock.

You can take advantage of Spring AOP’s surround-notification nature to do just that.

Maven dependency

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4. The RELEASE</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson-spring-boot-starter</artifactId>
        <version>3.11.5</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>
Copy the code

Here using redis redisson this third-party libraries to do distributed lock, redis configuration is not here, you can refer to the end of the article posted code address or specific can see this article zhaoxiaobin.net/Redis/Redis…

2. Develop custom annotations

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributionLock {
    /** * distributed lock key */
    String value(a) default "";

    /** * The wait time for the distributed lock */
    int waitTime(a) default 5 * 1000;

    /** * The position of the distributed lock key in the parameter list */
    int index(a) default- 1;
}
Copy the code

There are two modes based on the size of the lock:

  1. Coarse-grained: from annotationsvalueField specifies that the lock is shared by the same method (business) at compile time. Whoever tuned this method did it sequentially.
  2. Fineness: One parameter in the method’s argument list is used as the lock’skeyValues, such as a number, a serial number, and so on, are business unique parameters. If the same (same group) transaction allows different people to do at the same time, but the same person must be serial execution of the scene; byindexSpecified as akeyParameter position to.

3. Development facets

@Order(Integer.MIN_VALUE + 1)
@Aspect
@Component
@Slf4j
public class DistributionLockAspect {
    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(distributionLock)")
    public Object doAround(ProceedingJoinPoint point, DistributionLock distributionLock) throws Throwable {
        String methodName = point.getSignature().getName();
        if (StringUtils.isNotBlank(distributionLock.value())) {
            // The lock is coarse-grained. The name of the lock is specified in the DistributionLock annotation on the target method and is shared by the same method (business)
            return this.tryLock(point, distributionLock.value(), distributionLock.waitTime());
        } else if (distributionLock.index() >= 0) {// The lock is fine-grained, with the x th parameter of the target method as the lock name, which can be a business number, name, etc
            Object[] args = point.getArgs(); // Parameter list
            int index = distributionLock.index(); // The number of parameters in the parameter list is the key of the distributed lock
            if (args.length <= index) {
                log.error("No :{} argument on target method :{}", methodName, index);
                throw new RuntimeException("Target method :" + methodName + "There is no :" + index + "Parameters");
            }
            String key = args[index].toString();
            return this.tryLock(point, key, distributionLock.waitTime());
        } else {
            log.error("No key configured for a specific distributed lock, target method :{}", methodName);
            throw new RuntimeException("No key configured for a specific distributed lock, target method :"+ methodName); }}/** * only get, release lock, no exception, throw exception **@paramPointcut *@paramKey Key * of the distributed lock@paramWaitTime waitTime to get the lock *@return
     * @throws Throwable
     */
    private Object tryLock(ProceedingJoinPoint point, String key, int waitTime) throws Throwable {
        RLock disLock = redissonClient.getLock(key);
        try {
            // By default, the lock expires after 30 seconds. Every 30/3=10 seconds, the watchdog (daemon thread) will renew the lock, reset to 30 seconds
            boolean tryLock = disLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
            if(! tryLock) { log.error("Failed to acquire distributed lock :{}", key);
                // It is up to the business to throw an exception or return NULL or other business objects
// throw new RuntimeException(" Failed to acquire distributed lock ");
                return false;
            }
            return point.proceed(point.getArgs());
        } finally {
            // Only the thread that acquired the lock performs the lock release operation
            if(disLock.isHeldByCurrentThread()) { disLock.unlock(); }}}}Copy the code

4. Simulated test to deduct inventory

Business operations

@Service
public class DistributionLockDemo {
    /** * inventory */
    public static int count = 20;

    public boolean increment(a) {
        if (count > 0) {
            count--;
            return true;
        }
        return false;
    }

    public int get(a) {
        returncount; }}Copy the code

Simulated transaction initiation

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class DistributionLockTests {
    @Autowired
    private DistributionLockDemo distributionLockDemo;

    / * * * multi-threaded parallel processing 500 requests, buckle inventory * /
    @Test
    public void testDistributionLock(a) {
        long successCount = IntStream.range(0.500).parallel().filter(j -> distributionLockDemo.increment()).count();

        log.info("Success number :{}", successCount);
        log.info("Remaining inventory :{}", distributionLockDemo.get()); }}Copy the code

Here, we use Java8’s parallel stream to send 50 transactions simultaneously to simulate a 20-lock inventory without locking:

2021-06-30 18:10:20.942 [INFO] [main] [net.zhaoxiaobin.redisson.DistributionLockTests:35[] Success number:27
2021-06-30 18:10:20.942 [INFO] [main] [net.zhaoxiaobin.redisson.DistributionLockTests:36[] Surplus inventory:0
Copy the code

Mysql > alter table t2 add lock increment;

@DistributionLock(value = "incrementLock", waitTime = 1000)
public boolean increment(a) {
    if (count > 0) {
        count--;
        return true;
    }
    return false;
}
Copy the code
2021-06-30 18:11:30.722 [INFO] [main] [net.zhaoxiaobin.redisson.DistributionLockTests:35[] Success number:20
2021-06-30 18:11:30.722 [INFO] [main] [net.zhaoxiaobin.redisson.DistributionLockTests:36[] Surplus inventory:0
Copy the code

The above case is not too rigorous, in order to test the convenience, just tested the effect of a single cluster and not tested. Conditions can be connected to the database and a number of services to withhold the database data, to see how the effect of distributed lock.

5, distributed lock security

I believe many students know that Redis distributed lock is not so foolproof; Such as master-slave lock loss, as a result of switching, and NPC problems influence the safety of the lock, concrete can refer to this article zhaoxiaobin.net/Redis/Redis…

Advice:

  1. For businesses that require data to be absolutely correct, you must do a “bottom pocket” in the resource layer, such as optimistic locking of databases, etcCASSuch operations.
  2. With distributed locks, the purpose of “mutually exclusive” is accomplished in the upper layer. Although the lock will fail in extreme cases, it can prevent concurrent requests to the maximum extent in the top layer, reducing the pressure on the operation resource layer.

The code address

  • Github:github.com/senlinmu100…
  • Gitee:gitee.com/ppbin/redis…

Personal website

  • Github Pages
  • Gitee Pages