问题背景:
页面触发同步按钮,后台通过duubo调用服务的多个接口,进行读取写入,中转的时间也比较久,这种情况,如果只是在后台swagger触发接口,数据量十万以上,加了配置,倒是不会超时了,但是在页面点击同步时,直接卡主了,这时dubbo配置超时已经是不能够用的了,时间长的话会导致504网关超时,会很影响用户体验。
目标:
数据同步按钮点击后,页面直接返给友好提示,不再给用户展示调用过程的等待期;且由于数据量会很大,为保证数据同步的完整性,我这里采用对接口触发频率做限制。
执行方案:
使用多线程处理数据;AOP监测接口触发频率。
Show Code:
异步方案:
private static Semaphore sbytStatusSemaphore = new Semaphore(10, true);
private static ThreadPoolExecutor assignSbytStatusPoolExecutor =
new ThreadPoolExecutor(10, 10, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10));
public Result executeFuncA(){
//异步获取
try {
sbytStatusSemaphore.acquire();
} catch (InterruptedException e) {
log.error("CenterServiceImpl.executeFuncA:入队失败");
}
CompletableFuture.runAsync(() -> {
try {
//异步的方法
Result result = getHttzRecursion();
if (!result.isSuccess()) {
log.error("CenterServiceImpl.executeFuncA:同步失败" + result.getMessage());
}
} catch (Exception e) {
log.error("CenterServiceImpl.executeFuncA:同步异常" + e.getMessage());
} catch (Throwable e) {
log.error("CenterServiceImpl.executeFuncA:同步抛出异常" + e.getMessage());
} finally {
sbytStatusSemaphore.release();
}
}
, assignSbytStatusPoolExecutor);
//优先执行此返回结果
return Result.ok();
}
监听接口调用次数方案:
创建两个类,分别是注解类LimitTime 和工具类LimitTimeAspectUtil
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LimitTime {
// 访问次数,默认为10次
int time() default 10;
// 过期时间,时间戳间隔
long timeout() default 1;
}
@Component
@Order
@Aspect
@Slf4j
public class LimitTimeAspectUtil {
private ConcurrentHashMap<String, LimitReqDTO> limitMap = new ConcurrentHashMap<>();
@Pointcut("@annotation(limitTime)")
public void limit(LimitTime limitTime) {
}
@Around("limit(limitTime)")
public Object aroundLog(ProceedingJoinPoint joinpoint, LimitTime limitTime) throws Throwable {
//获取传入的最大访问次数
int time = limitTime.time();
//获取计算时间
long timeout = limitTime.timeout();
//获取访问方法
Object target = joinpoint.getTarget().getClass().getName();
String key = target.toString();
//如果第一次访问该方法
if (limitMap.get(key) == null) {
//新建一次对象存放访问信息
LimitReqDTO limitDTO = new LimitReqDTO();
limitDTO.setTime(time - 1);
limitDTO.setRefreshTime(System.currentTimeMillis());
limitMap.put(key, limitDTO);
} else {
//如果不是第一次访问,获取上次访问的信息
LimitReqDTO limitDTO = limitMap.get(key);
//如果和上次刷新时间比已经过期
if (System.currentTimeMillis() - limitDTO.getRefreshTime() > timeout) {
//将对象中的刷新时间和访问次数刷新
limitDTO.setRefreshTime(System.currentTimeMillis());
limitDTO.setTime(time);
limitMap.put(key, limitDTO);
}
//获取当前访问对象中的剩余访问次数
int t = (int) limitMap.get(key).getTime();
//如果访问次数大于0
if (t > 0) {
//允许访问,并将访问次数-1
limitDTO.setTime(--t);
} else {
//如果已经没有访问次数,返回错误信息
return Result.error("操作频繁,请稍后再试。");
}
}
//打印信息
log.error("剩余次数:" + limitMap.get(key).getTime() + " 方法名称:" + key);
return joinpoint.proceed();
}
}
此注解类,应用在controller的方法上
@PostMapping("/executeFuncA")
@LimitTime(time = 1, timeout = 60000)
public Result executeFuncA() {
return service.executeFuncA();
}