第一步,切片类使用RedisDistributedLockAspect监听@RedisLock主解
RedisConfig配置类
@Configuration
public class RedisConfig {
@Bean
public RedisDistributedLockAspect createRedisDistributedLockAspect(ObjectProvider<RedisTemplate> redisTemplate){
RedisDistributedLockAspect aspect = new RedisDistributedLockAspect();
aspect.setRedisTemplate(redisTemplate.getIfAvailable());
return aspect;
}
}
RedisLock源码类
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisLock {
/** 缓存Key,支持SPEL表达式 **/
String value() default "";
String tips() default "重复请求";
/** 过期时间(毫秒) **/
long expire() default 3000L;
/** 最大过期时间(毫秒) **/
long maxExpire() default 300000L;
/** 方法执行完释放 **/
boolean quitRelease() default true;
}
RedisDistributedLockAspect 的源码,最关键了,逻辑都在这里面
@Aspect
public class RedisDistributedLockAspect {
public static final String KEY_PREFIX = "DUPLICATION_SUBMIT:";
private RedisTemplate redisTemplate;
public RedisDistributedLockAspect() {
}
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Pointcut("@annotation(com.dstcar.common.utils.lock.RedisLock)")
public void getPointcut() {
}
@Around("getPointcut()")
public Object doBefore(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature)point.getSignature();
Method method = signature.getMethod();
Object result;
if (null != method && method.isAnnotationPresent(RedisLock.class)) {
RedisLock redisLock = (RedisLock)method.getAnnotation(RedisLock.class);
String lockKey = "DUPLICATION_SUBMIT:" + SpELAspectSupport.getKeyValue(point, redisLock.value());
String requestId = UUID.randomUUID().toString();
long expireTime = redisLock.expire();
if (redisLock.quitRelease()) {
expireTime = redisLock.maxExpire();
}
boolean res = this.tryGetDistributedLock(lockKey, requestId, expireTime);
if (!res) {
throw new DistributedLockException(redisLock.tips());
}
try {
result = point.proceed();
} finally {
if (redisLock.quitRelease()) {
this.releaseDistributedLock(lockKey);
}
}
} else {
result = point.proceed();
}
return result;
}
public boolean tryGetDistributedLock(String lockKey, String requestId, long expireTime) {
return this.redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expireTime, TimeUnit.MILLISECONDS);
}
public boolean releaseDistributedLock(String lockKey) {
return this.redisTemplate.delete(lockKey);
}
}
第二步,接口层注解的使用
@PostMapping("/addInstallmentOrder")
@RedisLock(value = "'addInstallmentOrder_'+#addInstallmentOrderDTO.sysUserId", tips = "请勿重复提交", quitRelease = false)
public Results addInstallmentOrder(@RequestBody @Validated AddInstallmentOrderDTO addInstallmentOrderDTO) {
StopWatch stopWatch = new StopWatch();
stopWatch.start("开始创建金融单");
String financeLoanOrder= financeLoanOrderService.addInstallmentOrder(addInstallmentOrderDTO);
//Thread.sleep(3000L);
stopWatch.stop();
log.info("结束创建金融单:{}s", stopWatch.getLastTaskTimeMillis()*0.001);
return succeed(financeLoanOrder);
}
针对源码,对分布式锁不起作用等函数的解析如下:
1、value值不填默认是"",可以在后面加上**#拼接请参的字段作为唯一的key值**
2、源码的tryGetDistributedLock()看作加锁lock()的操作,releaseDistributedLock()看作释放锁unlock()的操作
3、expire 过期时间 3000L指的是过期时间3s,maxExpire 300000L指的是最大过期时间300s,quitRelease() default true 默认为true的话,指的是方法执行完就释放锁了。
业务场景:
一、业务处理逻辑比较快的,1秒以内的,可以用StopWatch类判断出耗时时间。 记得设置quitRelease = false
因为:如果设置为true,1s以内执行完逻辑代码之后就释放锁了,
相同时间的请求又会请求进来不受限制了,这样看起来就会有重复的数据。当设置为false时,expire 起了作用,3s内即使逻辑代码走完也不会释放锁,等3s后才允许请他请求进来,这样同一个请求不会生成多条记录,保证了接口的幂等性。
二、业务处理逻辑比较慢的,可以设置quitRelease = true
因为:假如业务耗时是5秒,那么在这5秒内方法还没执行完,此时maxExpire最大过期数起了作用,自动过期时间不再等于expire3s而是等于maxExpire300s,所以3秒后锁也不会自动释放,要等业务方法走完才会释放锁。即使方法走完也没释放锁,那么锁也会在设置的maxExpire时间后自动释放的,看源码得出的结论。
重复测试接口工具:Fiddler