必须统一通过asyncnotifier.notifyasync()走start()物理通道,即asynccontext.start()提交至业务线程池,禁用所有绕过asynccontext的异步方式,并经编译期工具封装与运行期审计双重保障。

要确保复杂业务重构中所有异步通知组件严格走 start() 物理通道,核心在于统一管控异步任务的启动入口、剥离对容器默认线程模型的隐式依赖,并建立可验证的执行路径约束。这不是靠开发自觉,而是靠架构层强制。
明确 start() 物理通道的真实含义
在 Tomcat NIO 场景下,“走 start() 物理通道”本质是指:
- 所有异步逻辑必须通过
AsyncContext.start(Runnable)显式触发; - 该
Runnable必须被提交至业务专用线程池(而非 Tomcat 的ioExecutor或pool); - 不允许出现
CompletableFuture.runAsync()、@Async、new Thread().start()、ScheduledExecutorService直接调度等绕过AsyncContext的方式; -
start()调用前必须已调用request.startAsync(),且上下文未超时或失效。
建立编译期与运行期双重拦截机制
-
编译期卡点:在基础 SDK 中提供
AsyncNotifier工具类,封装唯一合法入口:public class AsyncNotifier { private static final Executor BUSINESS_EXECUTOR = ... // 注入业务线程池 public static void notifyAsync(HttpServletRequest req, Runnable task) { AsyncContext ctx = req.getAsyncContext(); if (ctx == null) { throw new IllegalStateException("Async not started. Must call request.startAsync() first."); } ctx.start(() -> BUSINESS_EXECUTOR.execute(task)); // 强制走 start + 业务池 } }所有业务模块禁止直接调用
ctx.start(),必须使用该工具——通过内部私有构造、SPI 接口或字节码插桩(如 ByteBuddy)限制非法调用。 -
运行期审计:
- 在
AsyncListener.onStartAsync()中埋点,记录AsyncContext创建栈和首次start()调用栈; - 启动时扫描所有
@Component类,反射检查是否含有Future/@Async/new Thread等高危模式,失败则抛出ApplicationContextException; - 日志中统一打标
async-channel=start-physical,便于链路追踪系统过滤识别。
- 在
改造存量通知组件的三步法
第一步:隔离 IO 与业务逻辑
将 HTTP/RPC 调用、MQ 发送、DB 写入等耗时操作全部抽离为纯函数,不带任何线程调度语义;-
第二步:统一封装为
AsyncTask接口public interface AsyncTask { void execute(DataContext context) throws Exception; String getChannelName(); // 如 "inventory-notify", 用于监控归类 }所有通知实现类实现此接口,由统一调度器加载;
-
第三步:注入式启动控制
在 Controller 层统一收口:@PostMapping("/order/paid") public void onPaid(@RequestBody OrderEvent event, HttpServletRequest req) { AsyncContext ctx = req.startAsync(); ctx.setTimeout(30_000); AsyncNotifier.notifyAsync(req, () -> { try { inventoryNotifyTask.execute(new DataContext(event)); ctx.complete(); } catch (Exception e) { log.error("Notify failed", e); ctx.complete(); // 避免悬挂,失败也 complete } }); }
这样,无论通知逻辑多复杂、依赖多少中台服务,其物理执行路径都收敛到 start() → 业务线程池 → 统一异常兜底,彻底规避 ioExecutor 污染与线程泄漏风险。










