模型发送请求使用异步执行分析任务
java
@PostMapping(value = "/ai_ops", produces = "text/event-stream;charset=UTF-8")
public SseEmitter aiOps() {
SseEmitter emitter = new SseEmitter(600000L); // 10分钟超时(告警分析可能较慢)
executor.execute(() -> {
try {
// 调用 AiOpsService 执行分析流程
Optional<OverAllState> overAllStateOptional = aiOpsService.executeAiOpsAnalysis(chatModel, toolCallbacks);
OverAllState state = overAllStateOptional.get();
logger.info("AI Ops 编排完成,开始提取最终报告...");
// 提取最终报告
Optional<String> finalReportOptional = aiOpsService.extractFinalReport(state);
// 输出最终报告
if (finalReportOptional.isPresent()) {
// 发送
}
}
});
return emitter;
}代码中的executor就是把agent请求的任务交给了另一个线程,让他去执行,避免了等待,提高了高并发情况下的稳定性
异步执行分析任务,为什么需要另一个线程?
简单来说,这是为了 “不让一个人(线程)累死,也不让他堵住后面的所有人”。
想象一下餐厅的运作:
- Tomcat 的请求处理线程 = 餐厅门口接待客人的 迎宾员。
- 耗时的 AI 分析任务 = 需要厨师花很长时间才能做好的 复杂菜肴。
同步方式(错误示范)
迎宾员(Tomcat 线程)接到一个点餐(请求)后,自己跑到后厨,亲自盯着厨师做完这道菜(耗时 5 分钟),然后端着菜给客人,再回来迎接下一位客人。
问题:
- 效率极低:迎宾员 5 分钟只能服务一位客人。
- 服务瘫痪:如果同时来了 10 位客人,就需要 10 位迎宾员,但餐厅可能总共就 5 位迎宾员。后来的客人只能排队等待,甚至被拒绝服务。这就是你说的 “拖垮服务器”。
异步方式(代码的做法)
迎宾员(Tomcat 线程)接到点餐后,立刻把菜单交给一个专门负责传菜的 “跑单员”(自定义线程池 executor),然后马上回头去接待新的客人。
- 跑单员 的任务很简单:拿着菜单,等着厨师(AI 分析任务)把菜做好,然后端给客人。
- 厨师 在后厨慢慢做菜,完全不影响前台的迎宾工作。
好处:
- 迎宾员解放:负责网络请求的 Tomcat 线程可以瞬间返回,继续处理成百上千的其他请求,并发能力大增。
- 任务有序执行:耗时的 AI 任务会被放到一个独立的、可控的 “跑道”(线程池)上排队执行,不会让服务器乱套。
一句话总结: 这是网络服务器处理耗时任务的 标准工业实践,目的是为了将 “管理请求的轻量工作” 与 “处理业务的重量工作” 分离开,保证服务的整体稳定和高效
改进建议:如果想更优雅,可以改用 CompletableFuture + SseEmitter 的组合,让代码更健壮(超时控制、异常传递)。
Optional < >一个帮忙判断是否是空的包装盒
Optional<String> 是什么?
这是一个用于 “安全地告诉调用方:我可能有值,也可能没有” 的容器。
为什么会有 Optional?
在 Java 中,一个方法如果可能返回 null,调用方很容易忘记检查,直接调用 null 对象的方法就会触发 NullPointerException(空指针异常),导致程序崩溃。Optional 就像一个 “漂亮的包装盒”,它明确地告诉你:“里面的东西可能存在,也可能是空的,你必须拆开检查后才能使用”。
在这个场景中
extractFinalReport(state)方法(从 AI 多 Agent 的执行结果中提取最终的告警报告)不能保证 100% 成功。如果内部流程出错,报告字段缺失等,就无法提取出报告。- 于是,这个方法返回一个
Optional<String>:- 如果成功:盒子里装着报告内容(
Optional.of("报告内容"))。 - 如果失败:盒子里是空的(
Optional.empty())。
- 如果成功:盒子里装着报告内容(
isPresent()的作用
isPresent()、ifPresent 方法的作用
它就是 “检查包装盒是否为空” 的官方方法。
- 返回
true:说明盒子里有东西,可以安全地用.get()方法拿出来。 - 返回
false:说明盒子是空的,不能拿,需要走其他逻辑(比如返回错误信息)。
你看到的这段代码
java
if (finalReportOptional.isPresent()) {
// 发送
}它的完整逻辑应该是:
java
if (finalReportOptional.isPresent()) {
// 1. 用 .get() 方法安全地取出报告内容
String report = finalReportOptional.get();
// 2. 通过 SSE 发送给客户端
emitter.send(report);
// 3. 表示任务完成,可以关闭连接了
emitter.complete();
} else {
// 盒子是空的,说明提取报告失败
emitter.send("系统内部错误:无法生成最终报告,请检查日志。");
// 以异常形式完成连接
emitter.completeWithError(new RuntimeException("No report extracted"));
}更优雅的写法: 使用 ifPresent 方法,代码更简洁,也完全避免了 get() 可能带来的风险:
java
finalReportOptional.ifPresentOrElse(
report -> { emitter.send(report); emitter.complete(); }, // 有值
() -> { emitter.send("无法生成报告"); emitter.completeWithError(...); } // 无值
);