设置函数超时时间

缘由

今天在回归问题的时候发现一个bug,在统计表中数量的时候,有一个刷新按钮,可以获取最新的表中的数据数量,但是bug是在点击刷新的时候会一直处于刷新状态,思考过后,找出问题缘由

问题:在点击刷新按钮的时候,后台通过sql去查询指定数据源下的指定表的数量,在获取的时候后台接口可能由于数据库连接问题,导致接口一直没有返回结果,所以前台中表的数据展示按钮一直处于刷新状态,最后接口到达了前台设置的超时时间30s后,接口连接中断,然后导致一直处于刷新状态。

解决方法

设置接口超时时间,当时间超过指定时间之后,就返回出一个错误信息,让前台不要再等待了直接进行下一步操作就行

想要给每一个接口都设置超时时间,如果在每一个接口中判断等待一定时间的时候函数还是没有执行完毕,则返回出错误信息,有两种实现方式

异步线程加切面方式

将耗时操作使用线程来封装,指定线程的超时时间

使用 ExecutorService 结合 Future 来实现超时控制

通过Callable创建一个可返回的的结果,并且可能抛出异常的任务

利用ExecutorService.submit 方法来提交Callable到线程池中,并生成Future对象

通过使用Future对象中的get方法来获取Callable的返回值,get可以设置超时时间

同时设置利用AOP切面 获取指定Controller层的执行开始时间和结束时间

注解实现

创建一个自定义注解,用于标记需要监控运行时间的 Controller 方法:

1
2
3
4
5
6
7
8
9
10
11
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseTimeout {
long timeout() default 2000; // 默认超时时间为 2000 毫秒
String message() ; // 设置错误返回信息
}

切面处理类

使用 AOP 来实现对标记了 @ResponseTimeout 注解的方法的运行时间监控:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.nuyoah.config;

import com.nuyoah.utils.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

@Aspect
@Component
@EnableAsync
public class ExecutionTimeMonitorAspect {

@Autowired
private Executor taskExecutor;

@Around("@annotation(responseTimeout)")
public Object handlerResponseTimeout(ProceedingJoinPoint joinPoint, ResponseTimeout responseTimeout) throws Throwable {
// 处理异步任务的执行和结果获取 supplyAsync用于异步执行一个有返回值的任务
CompletableFuture<Object> future = CompletableFuture.supplyAsync(() -> {
try {
return joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}, taskExecutor);

try {
// 设置超时时间获取,如果时间到了还没有执行结束则会报错,但是函数还是会继续执行
return future.get(responseTimeout.timeout(), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
throw new TimeoutException(StringUtils.hasText(responseTimeout.message()) ? responseTimeout.message() : e.getMessage());
}
}
}

异步任务执行器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfig {

@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
}