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-Plus
the ecology, Lock4j
it provides support redission
, redisTemplate
, zookeeper
and 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次
相同条件:第一次请求会加锁,等待业务处理完成返回结果但不释放锁;第二次请求直接获取锁,若获取锁失败则执行失败策略抛出异常,若获取成功执行业务并加锁。