SpringBoot + Lock4j implements high-performance distributed locks

1 Introduction

  In distributed business development, distributed locks need to be added in many scenarios. In the specific practice process, R&D personnel need to implement it by themselves, resulting in inconsistent implementation methods, different code styles, and difficult maintenance.
  In Mybatis-Plusthe ecology, Lock4jit provides support redission, redisTemplate, zookeeperand distributed lock components, which are easy to use, powerful and scalable.
  Gitee address: https://gitee.com/baomidou/lock4j

2. Simple example

  Write sample codes using redisTemplate as the underlying implementation of distributed locks. The other two solutions can be realized by referring to the official website documents.

  • create project
  • Modify pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.c3stones</groupId>
    <artifactId>spring-boot-lock4j-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.7</version>
    </parent>

    <dependencies>
        <!--若使用redisTemplate作为分布式锁底层-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
            <version>2.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • Configure redis information
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    
# Lock4j配置
#lock4j:
#  acquire-timeout: 3000 #等待时长。默认值3s,可不设置
#  expire: 30000 #过期时间,防止死锁。默认值30s,可不设置
#  primary-executor: com.baomidou.lock.executor.RedisTemplateLockExecutor #默认redisson>redisTemplate>zookeeper,可不设置
#  lock-key-prefix: lock4j #锁key前缀。默认值lock4j,可不设置
  • When an exception occurs in the unified response class
      , the global exception handling class formats the exception information into a unified response class output.
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * 响应工具类
 *
 * @param <T>
 * @author CL
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class R<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    private Boolean code;

    private String msg;

    private T data;

    public static <T> R<T> ok() {
        return restResult(null, Boolean.TRUE, null);
    }

    public static <T> R<T> ok(T data) {
        return restResult(data, Boolean.TRUE, null);
    }

    public static <T> R<T> ok(T data, String msg) {
        return restResult(data, Boolean.TRUE, msg);
    }

    public static <T> R<T> failed() {
        return restResult(null, Boolean.FALSE, null);
    }

    public static <T> R<T> failed(String msg) {
        return restResult(null, Boolean.FALSE, msg);
    }

    public static <T> R<T> failed(T data) {
        return restResult(data, Boolean.FALSE, null);
    }

    public static <T> R<T> failed(T data, String msg) {
        return restResult(data, Boolean.FALSE, msg);
    }

    private static <T> R<T> restResult(T data, Boolean code, String msg) {
        R<T> apiResult = new R<>();
        apiResult.setCode(code);
        apiResult.setData(data);
        apiResult.setMsg(msg);
        return apiResult;
    }

}
  • Global exception handling class
/**
 * 全局异常处理
 *
 * @author CL
 */
@Log4j2
@RestControllerAdvice
public class WebExceptionAdvice {

    /**
     * 异常处理
     *
     * @param ex 异常
     * @return {@link R}
     */
    @ExceptionHandler(value = Exception.class)
    public R<?> errorHandler(Exception ex) {
        log.error(ex);
        return R.failed(ex.getMessage());
    }

}
  • Custom distributed lock failure strategy (optional)
import com.baomidou.lock.LockFailureStrategy;
import com.baomidou.lock.exception.LockFailureException;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 自定义获取锁失败策略
 *
 * @author CL
 */
@Component
public class MyLockFailureStrategy implements LockFailureStrategy {

    @Override
    public void onLockFailure(String key, Method method, Object[] arguments) {
        throw new LockFailureException("处理中,请稍后");
    }

}
  • five test methods
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.lock.executor.RedisTemplateLockExecutor;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * 测试Service
 *
 * @author CL
 */
@Service
@RequiredArgsConstructor
public class TestService {

    /**
     * 测试无锁
     *
     * @param processTime 业务处理时间
     * @return {@link Long}
     */
    public Long test(long processTime) {
        try {
            Thread.sleep(processTime * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return processTime;
    }

    /**
     * 测试默认分布式锁
     *
     * @param processTime 业务处理时间
     * @return {@link Long}
     */
    @Lock4j
    public Long test1(long processTime) {
        return test(processTime);
    }

    /**
     * 测试自定义配置分布式锁
     * <p>
     * keys: 锁名称,全局唯一<br/>
     * expire: 过期时间,防止死锁<br/>
     * acquireTimeout: 等待时间,获取不到则执行失败策略<br/>
     * </p>
     *
     * @param processTime 业务处理时间
     * @return {@link Long}
     */
    @Lock4j(keys = {"#processTime"}, expire = 60000, acquireTimeout = 1000)
    public Long test2(long processTime) {
        return test(processTime);
    }

    private final LockTemplate lockTemplate;

    /**
     * 测试手动获取分布式锁
     *
     * @param processTime 业务处理时间
     * @return {@link Long}
     */
    public Long test3(long processTime) {
        // 获取锁
        final LockInfo lockInfo = lockTemplate.lock("custom:" + processTime, 30000L, 2000L, RedisTemplateLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("处理中,请稍后");
        }

        // 获取锁成功
        try {
            test(processTime);
        } finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }

        return processTime;
    }

    /**
     * 测试5秒内只能访问1次
     *
     * @param processTime 业务处理时间
     * @return {@link Long}
     */
    @Lock4j(keys = {"#processTime"}, acquireTimeout = 0, expire = 5000, autoRelease = false)
    public Long test4(long processTime) {
        return processTime;
    }

}
  • test interface
import com.c3stones.common.R;
import com.c3stones.service.TestService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试Controller
 *
 * @author CL
 */
@RestController
@RequestMapping()
@RequiredArgsConstructor
public class TestController {

    private final TestService testService;

    /**
     * 测试无锁
     *
     * @param processTime 业务处理时间
     * @return {@link R}
     */
    @RequestMapping("/test")
    public R<Long> test(long processTime) {
        return R.ok(testService.test(processTime));
    }

    /**
     * 测试默认分布式锁
     *
     * @param processTime 业务处理时间
     * @return {@link R}
     */
    @RequestMapping("/test1")
    public R<Long> test1(long processTime) {
        return R.ok(testService.test1(processTime));
    }

    /**
     * 测试自定义配置分布式锁
     *
     * @param processTime 业务处理时间
     * @return {@link R}
     */
    @RequestMapping("/test2")
    public R<Long> test2(long processTime) {
        return R.ok(testService.test2(processTime));
    }

    /**
     * 测试手动获取分布式锁
     *
     * @param processTime 业务处理时间
     * @return {@link R}
     */
    @RequestMapping("/test3")
    public R<Long> test3(long processTime) {
        return R.ok(testService.test3(processTime));
    }

    /**
     * 测试5秒内只能访问1次
     *
     * @param processTime 业务处理时间
     * @return {@link R}
     */
    @RequestMapping("/test4")
    public R<Long> test4(long processTime) {
        return R.ok(testService.test4(processTime));
    }

}
  • startup class
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类
 *
 * @author CL
 */
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

3. Test

  Test plan: Send the same request twice quickly manually, assuming that the business processing time is 5 seconds, check whether the response result satisfies the distributed lock.

  • Test
      the same condition without locks: the first request and the second request will not be locked, and wait for the business processing to complete and return the result.
  • Test the same conditions as the default distributed lock
      : the first request will lock, wait for the business processing to complete and return the result and release the lock; the second request waits for 3 seconds to acquire the lock by default, and if the lock acquisition fails, the execution failure strategy throws an exception.

  •   Test the same conditions for custom configuration of distributed locks: the first request will be locked, wait for the business processing to complete and return the result and release the lock; the second request will wait for 1 second to acquire the lock, if the lock fails to be acquired, the execution failure strategy will be thrown abnormal.
  • 测试手动获取分布式锁
      相同条件:第一次请求会加锁,等待业务处理完成返回结果并释放锁;第二次请求自定义等待2秒获取锁,若获取锁失败则执行失败策略抛出异常。
  • 测试5秒内只能访问1次
      相同条件:第一次请求会加锁,等待业务处理完成返回结果但不释放锁;第二次请求直接获取锁,若获取锁失败则执行失败策略抛出异常,若获取成功执行业务并加锁。

4. 项目地址

  spring-boot-lock4j-demo

Guess you like

Origin blog.csdn.net/qq_48008521/article/details/129153814