问题:在一个业务流程中,会有两个微服务近乎同事调用一个查询第三方的接口,导致需要调用两次第三方接口,而无法用到查询到第三方接口返回结果的缓存值,于是想做个分布式的锁,来减少调用第三方次数和利用缓存值。
实现采用如下三板斧:
第一步:先建立一个注解类,prefixKey为业务场景值, paramKey为具体某个业务值,例如文中用到的userId
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* redis锁
* @since 2021/1/11 14:35
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface RedisLock {
String prefixKey() default "";
String paramKey() default "";
long timeOut() default 5*1000;
TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}
第二步: 创建redis分布式锁的切面,主要是在方法调用前加锁,在方法调用后去掉锁
import java.lang.reflect.Field;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* redis锁切面
* @since 2021/1/11 14:37
*/
@Aspect
@Component
public class RedisLockAspect {
private final static Logger logger = LoggerFactory.getLogger(RedisLockAspect.class);
@Autowired
private RedisUtil redisUtil;
@Pointcut("@annotation(com.annotation.RedisLock)")
public void RedisLockAspect() {
}
@Around("RedisLockAspect()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object object = null;
RedisLock redisLock = getRedisLockInfo(proceedingJoinPoint);;
String redisLockKey = getRedisLockKey(proceedingJoinPoint, redisLock);
logger.info("RedisLockAspect start ");
try {
redisUtil.getLock(redisLockKey, redisLock.timeOut());
object = proceedingJoinPoint.proceed();
} finally {
redisUtil.unlock(redisLockKey);
}
logger.info("RedisLockAspect end ");
return object;
}
/**
* 组合分布式redis的锁, 使用prefixKey+paramKey组合值
*/
public String getRedisLockKey(ProceedingJoinPoint proceedingJoinPoint, RedisLock redisLock) {
Object[] args = proceedingJoinPoint.getArgs();
if(args != null && args.length>0){
for (Object object : args) {
try {
Field declaredField = object.getClass().getDeclaredField(redisLock.paramKey());
declaredField.setAccessible(true);
return redisLock.prefixKey()+declaredField.get(object);
} catch (NoSuchFieldException | IllegalAccessException e) {
logger.warn("getRedisLockKey warn", e);
}
}
}
return redisLock.prefixKey();
}
private RedisLock getRedisLockInfo(ProceedingJoinPoint proceedingJoinPoint) {
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
return methodSignature.getMethod().getAnnotation(RedisLock.class);
}
}
第三步:redisUtil中的一些加锁,解锁,功能考虑的比较简单,用setIfAbsent来加锁,delete来解锁
public boolean getLock(String redisLockKey, long timeOut) {
while(true){
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent(redisLockKey, "lock");
if(lock){
stringRedisTemplate.expire(redisLockKey, timeOut, TimeUnit.MILLISECONDS);
return true;
}
// 减少循环次数
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// 等待时间出现问题
log.warn("currentThread sleep warn", e);
}
}
}
public void unlock(String redisLockKey) {
stringRedisTemplate.delete(redisLockKey);
}