写在前面
本文使用 Redis 实现的分布式锁,通过注解的方式应用,可以避免了对业务代码的侵入。
使用版本
jdk: 12
redis:5.0.4-alpine
spring-boot-starter-parent : 2.3.1.RELEASE
redission:3.11.4
文件说明
- AnnotationResolver.java : 注解解析器,参考了注解实现aop、aop动态传参、使用注解aop优化代码的文章实现,可以在注解上取得参数变量
- DistributedLock.java : 声明注解
- DistributedLockAop.java : 分布式锁实现
- RedissionConfig.java : Redission 配置文件
代码实现
ps: spring-boot-starter-parent 版本号2.3.1.RELEASE
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<!-- redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
application.yml
spring:
redis:
host: 127.0.0.1
port: 6789
password: root
database: 0
timeout: 3S
AnnotationResolver.java
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
public class AnnotationResolver {
private static AnnotationResolver resolver ;
public static AnnotationResolver newInstance(){
if (resolver == null) {
return resolver = new AnnotationResolver();
}else{
return resolver;
}
}
/**
* 解析注解上的值
* @param joinPoint
* @param str 需要解析的字符串
* @return
*/
public Object resolver(JoinPoint joinPoint, String str) {
if (str == null) return null ;
Object value = null;
if (str.matches("#\\{\\D*\\}")) {// 如果name匹配上了#{},则把内容当作变量
String newStr = str.replaceAll("#\\{", "").replaceAll("\\}", "");
if (newStr.contains(".")) { // 复杂类型
try {
value = complexResolver(joinPoint, newStr);
} catch (Exception e) {
e.printStackTrace();
}
} else {
value = simpleResolver(joinPoint, newStr);
}
} else { //非变量
value = str;
}
return value;
}
private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
String[] strs = str.split("\\.");
for (int i = 0; i < names.length; i++) {
if (strs[0].equals(names[i])) {
Object obj = args[i];
Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);
Object value = dmethod.invoke(args[i]);
return getValue(value, 1, strs);
}
}
return null;
}
private Object getValue(Object obj, int index, String[] strs) {
try {
if (obj != null && index < strs.length - 1) {
Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);
obj = method.invoke(obj);
getValue(obj, index + 1, strs);
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private String getMethodName(String name) {
return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
}
private Object simpleResolver(JoinPoint joinPoint, String str) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < names.length; i++) {
if (str.equals(names[i])) {
return args[i];
}
}
return null;
}
}
DistributedLock.java
import java.lang.annotation.*;
/**
* DistributedLock
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DistributedLock {
String lockKey() default "NONE";
String tip() default "正在操作中";
}
DistributedLockAop.java
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
@Slf4j
public class DistributedLockAop {
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(com.example.demo.aop.DistributedLock)")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
Method method = methodSignature.getMethod();
DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
String lockKey = (String) AnnotationResolver.newInstance().resolver(joinPoint, distributedLock.lockKey());
String failGetLockTip = (String) AnnotationResolver.newInstance().resolver(joinPoint, distributedLock.tip());
// 可自定义规则
lockKey= "LOCK:"+method.getName()+":"+lockKey;
RLock lock = redissonClient.getLock(lockKey);
boolean locked = false;
try {
locked = getLock(lock, lockKey);
if (!(locked)) {
log.info("已经有人占用了锁...");
throw new InterruptedException(failGetLockTip);
}
log.info("获取到锁...,{}",lockKey);
return joinPoint.proceed();
} finally {
try {
if (locked) {
lock.unlock();
}
} catch (Exception ignore) {}
}
}
private boolean getLock(RLock rLock, String lockKey) {
try {
// leaseTime: 锁有效时间 防止锁一直被占用
// waitTime: 等待时间 没有获取到锁继续等待的时间
return rLock.tryLock(1, 1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("Fail add lock, key: [{}] ", lockKey, e);
return false;
}
}
}
RedissionConfig.java
@Configuration
public class RedissionConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port).setPassword(password);
//添加主从配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
return Redisson.create(config);
}
}
TestService.java 业务类
import com.example.demo.aop.DistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class TestService {
@DistributedLock(lockKey ="#{id}",tip = "正在获取中...")
public String getMsg(String id) {
try {
// 模拟业务运行 0.5s
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "MSG: "+id;
}
}
测试类
@SpringBootTest
class DemoApplicationTests {
@Autowired
private TestService testService;
@SneakyThrows
@Test
public void test() {
String id = "1";
// 模拟并发
for (int i = 0; i < 50; i++) {
new Thread(()-> {
try {
testService.getMsg(id);
} catch (Exception ignored) {}
}).start();
}
new CountDownLatch(1).await(); //防止程序没执行完退出
}
}
ps: 以上的代码直接复制到项目可以直接跑,后面为解释。
使用方式:
在方法上使用@DistributedLock
注解即可,lockKey
为唯一关键字,这些可以使用参数做为唯一值,tip
表示如果没有获取到锁抛出异常的提示内容
。
@DistributedLock(lockKey ="#{key}",tip = "正在获取中...")
public String test(String key) {
//... 业务
}
测试运行:
在测试类里模拟并发情况
效果展示:
业务0.1s执行完之后,陆续有人获取锁。而为什么获取锁的都在前面呢?因为在锁的配置(rLock.tryLock(1, 1, TimeUnit.SECONDS);
)使用了waitTime
为1s,所以会等一秒再获取锁,没获取到才放弃。
前面的线程0.1s
就可以获取到锁就先输出了获取到锁
,后面的线程1s
后一并到期,就都输出了已经有人占用了锁
。
--- [ Thread-135] com.example.demo.aop.DistributedLockAop : 获取到锁...,LOCK:getMsg:1
--- [ Thread-104] com.example.demo.aop.DistributedLockAop : 获取到锁...,LOCK:getMsg:1
--- [ Thread-108] com.example.demo.aop.DistributedLockAop : 获取到锁...,LOCK:getMsg:1
--- [ Thread-145] com.example.demo.aop.DistributedLockAop : 获取到锁...,LOCK:getMsg:1
--- [ Thread-136] com.example.demo.aop.DistributedLockAop : 获取到锁...,LOCK:getMsg:1
--- [ Thread-110] com.example.demo.aop.DistributedLockAop : 获取到锁...,LOCK:getMsg:1
--- [ Thread-125] com.example.demo.aop.DistributedLockAop : 获取到锁...,LOCK:getMsg:1
--- [ Thread-146] com.example.demo.aop.DistributedLockAop : 获取到锁...,LOCK:getMsg:1
--- [ Thread-148] com.example.demo.aop.DistributedLockAop : 获取到锁...,LOCK:getMsg:1
--- [ Thread-106] com.example.demo.aop.DistributedLockAop : 获取到锁...,LOCK:getMsg:1
--- [ Thread-142] com.example.demo.aop.DistributedLockAop : 已经有人占用了锁...
--- [ Thread-119] com.example.demo.aop.DistributedLockAop : 已经有人占用了锁...
--- [ Thread-105] com.example.demo.aop.DistributedLockAop : 已经有人占用了锁...
--- [ Thread-109] com.example.demo.aop.DistributedLockAop : 已经有人占用了锁...
...