这里用到了aop技术 分布式锁 简单来说就是 第一个请求成功获取锁后,后续的请求会因为无法获取锁而被阻塞或直接拒绝 这很好的解决了重复提交的问题
举个很简单的例子来说 假设用户 A 快速点击了两次提交按钮。
但是管理员发现这三次请求都对应同一个钥匙(比如 "order:123"),因此只会允许第一个请求进入,后续的请求会被拒绝。
管理员就相当于是切面逻辑 这时基于注解的切点表达式 被这个注解标记的方法 切面会在方法执行前后自动处理加锁和解锁的逻辑 下面的注解不仅仅可以标记方法也可以传参 下面都是可以传进去的参数
比如说一个方法可以这样 比如说这时有两个请求A和B
@Lock(formatter = "order:{orderId}", time = 5, unit = TimeUnit.SECONDS, block = false) public void submitOrder(String orderId) { // 提交订单的逻辑 }
`@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Lock {
/*** 加锁key的表达式,支持表达式*/
String formatter();/*** 加锁时长*/
long time() default 5;/*** 阻塞超时时间,默认2分钟,当block为true的时候生效*/
long waitTime() default 120;/*** 加锁时间单位*/
TimeUnit unit() default TimeUnit.SECONDS;/*** 方法访问完后要不要解锁,默认自动解锁*/
boolean unlock() default true;/*** 如果设定了true将在获取锁时阻塞等待waitTime时间*/
boolean block() default false;/*** 是否启用自动续期,如果使用自动续期则unlock必须设置为true*/
boolean startDog() default false;
}
@Aspect
public class LockAspect {
private final RedissonClient redissonClient;public LockAspect(RedissonClient redissonClient) {this.redissonClient = redissonClient;
}@Around("@annotation(lock)")
public Object handleLock(ProceedingJoinPoint pjp, Lock lock) throws Throwable {//锁key的格式化字符,此字符串中有spEL表达式String formatter = lock.formatter();Method method = AspectUtils.getMethod(pjp);Object[] args = pjp.getArgs();//得到锁的keyString redisKey = AspectUtils.parse(formatter, method, args);//获取锁阻塞等待的时间,如果是0表示去尝试获取锁,如果获取不到则结束long waitTime = 0;//阻塞等待获取锁if (lock.block()) {//根据时间单位转换成mswaitTime = lock.waitTime();}//加锁时长long time = lock.time();//启动看门狗自动续期if(lock.startDog()){time = -1;//如果设置自动续期必须在方法执行后释放锁if(!lock.unlock()){throw new BadRequestException(ErrorInfo.Msg.REQUEST_PARAM_ILLEGAL);}}//得到锁对象RLock rLock = redissonClient.getLock(redisKey);//尝试加锁boolean success = rLock.tryLock(waitTime,time, lock.unit());if (!success && !lock.block()) {//未阻塞要求的情况下未得到锁throw new BadRequestException(ErrorInfo.Msg.REQUEST_OPERATE_FREQUENTLY);}if (!success) {//阻塞情况下未得到锁,请求超时throw new RequestTimeoutException(ErrorInfo.Msg.REQUEST_TIME_OUT);}try {return pjp.proceed();} finally {if (lock.unlock()) {rLock.unlock();}}}`
第一步:解析锁 key
String redisKey = AspectUtils.parse(formatter, method, args);
根据注解中的 formatter = "order:{orderId}" 和参数 orderId = 123,生成锁的 key:redisKey = "order:123"
第二步:尝试获取锁
boolean success = rLock.tryLock(waitTime, time, lock.unit());
Redis 中的分布式锁会检查是否已经有线程持有 "order:123" 这个锁。
当前没有其他线程持有这个锁,因此请求 A 成功获取了锁
第三步:执行目标方法
return pjp.proceed();
第四步释放锁
if (lock.unlock()) {
rLock.unlock();
}
这时请求B 比如说是连续点击第二下的那个订单 他也会进入进来并且解析 尝试获取锁 但这时A已经进去了 无法获取 我们设置的规则是 因为 block = false,所以请求 B 不会等待,而是直接抛出异常:
,返回错误信息:“请求过于频繁”。