CompletableFuture + 异步Servlet:同步的姿势、异步的效果
这是一个务实但有效的工具。
假设在Servlet线程模型下的web容器,比如SpringBoot web starter下,
要处理一个慢接口,一般是文件操作、公网第三方接口调用等等,除了提高业务逻辑处理速度提高RT之外,
能做的防守动作是至少不要让这个慢接口拖垮Tomcat的worker线程池。
可供选择的方案可能很快是改技术栈为WebFlux,拥抱Netty; 或者升级老旧的JDK版本到21,直接上虚拟线程、肆意挥霍。
但是,WebFlux代码写法会让一些开发人员不习惯,直接上虚拟线程看似最简单,但是一些情况下也是需要付出一定的精力去确认的,比如:
synchronized 重量级锁 使得JVM没法使用yield使虚拟线程从载体线程卸载,导致虚拟线程与载体线程同时被阻塞。
我们需要确保业务逻辑所使用的第三方库中也不能包含这个关键字,排查起来还是需要一些细心的。
而 CompletableFuture 配合Servlet3.0异步机制,可以将耗时的业务逻辑转为异步任务,但是整个流程又确保是同步过程,可以收到不错的效果。
Controller有如下慢接口
private final SlowExecutorConfigV2 nnSlowExecutor;
@GetMapping("/nnslow")
public CompletableFuture<ApiResult<Long>> nnslow(@RequestParam Long id) {
return nnSlowExecutor.submit(() -> mpService.slow(id))
.orTimeout(15, TimeUnit.SECONDS)
.thenApply(ApiResult::success);
}
其中mpService.slow(id)预估需要执行几秒到十秒不等
我们上面是把这个逻辑封装成Callable丢给一个独立线程池,执行结果以CompletableFuture形式返回。
这背后,spring mvc 在任务提交到独立线程池的时候就释放worker线程回其线程池,由独立的ThreadPoolExecutor执行业务逻辑,
期间spring mvc保持连接,业务逻辑执行完成由实际干活的异步线程往连接中写CompletableFuture封装的结果。
@Component
public class SlowExecutorConfigV2 {
private static final Logger LOGGER = LoggerFactory.getLogger(SlowExecutorConfigV2.class);
/** 工作线程数 */
private static final int MAX_THREADS = 5;
/** 同时承载的慢任务上限(工作线程 + 队列) */
private static final int MAX_CONCURRENT = 30;
private final ExecutorService pool = new ThreadPoolExecutor(
MAX_THREADS,
MAX_THREADS,
60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(MAX_CONCURRENT - MAX_THREADS),
new ThreadFactory() {
private final AtomicInteger seq = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "slow-pool-" + seq.getAndIncrement());
}
},
new ThreadPoolExecutor.AbortPolicy());
/**
* 提交慢任务,返回 CompletableFuture 由调用方决定后续处理。
*
* 行为约定:
* - 正常完成:future 以任务返回值完成
* - 任务抛异常:原样转给 future(全局异常处理器记录真实原因)
* - 队列满被拒绝:future 以 BusinessException("业务繁忙") 完成,快速失败不堆积
*/
public <T> CompletableFuture<T> submit(Callable<T> task) {
CompletableFuture<T> future = new CompletableFuture<>();
try {
pool.execute(() -> {
try {
future.complete(task.call());
} catch (Exception e) {
future.completeExceptionally(e);
}
});
} catch (RejectedExecutionException e) {
LOGGER.error("slow 线程池已满,拒绝任务:{}", e);
future.completeExceptionally(new BusinessException("Slow业务繁忙"));
}
return future;
}
/** 容器关闭时停止接收新任务,等待已提交任务执行完,避免非 daemon 线程拖住 JVM 退出 */
@PreDestroy
public void shutdown() {
pool.shutdown();
}
}
浙公网安备 33010602011771号