Spring Boot使用SseEmitter分阶段返回PROCESSING与OK状态码的优化问题
问题根因分析
- 核心逻辑顺序错误:SseEmitter需要先返回给Spring MVC框架建立SSE连接,再异步执行业务逻辑推送消息。原代码把业务阻塞逻辑和send操作都放在请求线程执行,全部处理完才返回emitter,完全不符合SSE的非阻塞设计。
- @Async使用错误:直接加在接口方法上会导致整个方法异步执行,Spring MVC拿不到及时返回的SseEmitter实例,无法建立连接。
- 缺少连接完成回调:处理完业务后没有调用
emitter.complete()关闭连接,前端EventSource会触发超时自动重连,重复触发请求。 - 自定义线程池未使用:定义的
nonBlockingService线程池没有实际承载异步业务逻辑。 - 缺少异常兜底:没有处理业务执行异常的场景,容易出现连接泄漏。
优化后代码
1. Controller层修改
@RestController public class MainController { // 生产环境建议用ThreadPoolExecutor手动配置参数,避免OOM private ExecutorService nonBlockingService = Executors.newCachedThreadPool(); @CrossOrigin @GetMapping("/sse") public SseEmitter handleSse() { // 超时时间设置为略大于最长业务执行时间即可,这里设为5秒适配3秒业务 SseEmitter emitter = new SseEmitter(5000L); // 异步执行业务逻辑 nonBlockingService.execute(() -> { try { // 推送处理中状态,用value()返回状态码数值,也可以直接传枚举返回字符串 emitter.send(HttpStatus.PROCESSING.value()); // 执行业务逻辑 TestService.doSMG(); // 推送处理完成状态 emitter.send(HttpStatus.OK.value()); // 主动关闭SSE连接 emitter.complete(); } catch (Exception e) { // 异常场景关闭连接,避免泄漏 emitter.completeWithError(e); } }); // 立即返回emitter建立SSE连接 return emitter; } }
2. 前端代码适配
<html> <head> <script> var sse = new EventSource('http://localhost:8080/sse'); sse.onmessage = function (evt) { var el = document.getElementById('sse'); const status = Number(evt.data); if(status === 102) { el.appendChild(document.createTextNode('服务处理中...')); } else if(status === 200) { el.appendChild(document.createTextNode('服务处理完成')); // 处理完成主动关闭连接,避免自动重连 sse.close(); } el.appendChild(document.createElement('br')); }; // 异常兜底处理 sse.onerror = function(err) { console.error('连接异常:', err); sse.close(); }; </script> </head> <body> <p id = "sse"> </p> </body> </html>
额外优化建议
- 生产环境不要使用
Executors.newCachedThreadPool(),手动构造ThreadPoolExecutor,指定核心线程数、最大线程数、队列长度、拒绝策略,避免请求量过高引发OOM。 - 如果需要传递更复杂的业务信息,可以发送JSON格式的消息,前端接收后解析即可。
内容的提问来源于stack exchange,提问作者Nate Hiber
相关产品推荐
相关产品推荐

