Spring SSE+Angular5长耗时任务实现遇错求助:负载均衡断连问题
嘿,这种长异步任务用SSE替代WebSocket确实是个靠谱的选择,不过遇到服务端发送响应报错的情况,大概率是踩了几个常见的坑,我帮你梳理下排查方向和解决方案:
1. 先确认SSE连接的超时配置是否覆盖任务时长
负载均衡会断开连接的核心原因是空闲超时,而Spring的SseEmitter默认超时时间可能远短于你的5分钟任务时长,导致任务还没完成,Emitter就已经超时关闭了,后续发送自然会报错。
- 初始化
SseEmitter时显式设置更长的超时,比如给10分钟(完全覆盖5分钟任务):// 600000毫秒 = 10分钟 SseEmitter emitter = new SseEmitter(600000L); - 同时要配置你的负载均衡(比如Nginx、AWS ALB等)的空闲超时时间,确保它不会在任务执行过程中主动断开长连接。比如Nginx里要加:
proxy_read_timeout 600s; proxy_send_timeout 600s;
2. 避免异步任务丢失SSE连接上下文
如果你的长任务用了Spring的@Async注解,要注意异步线程会脱离原请求的上下文,如果没有正确持有SseEmitter的引用,很容易出现Emitter被GC回收或者连接已失效的情况。
- 建议用一个全局缓存(比如
ConcurrentHashMap)来关联任务ID和SseEmitter,确保异步任务能拿到有效的Emitter实例:private final ConcurrentHashMap<String, SseEmitter> emitterCache = new ConcurrentHashMap<>(); // 在启动任务时把Emitter存入缓存 String taskId = UUID.randomUUID().toString(); emitterCache.put(taskId, emitter); // 异步任务从缓存取出Emitter发送结果 @Async public void runLongTask(String taskId) { SseEmitter emitter = emitterCache.get(taskId); try { // 执行5分钟任务 Object result = longRunningProcess(); // 发送SSE消息,格式要正确(必须带data前缀) emitter.send(SseEmitter.event() .name("taskComplete") .data(result) .id(taskId)); emitter.complete(); // 主动关闭连接 } catch (Exception e) { emitter.completeWithError(e); // 发送错误信息 } finally { emitterCache.remove(taskId); // 清理缓存 } }
3. 处理SSE连接的异常与回调
一定要给SseEmitter绑定完成、超时回调,避免无效连接占用资源,同时能及时捕获连接断开的情况:
emitter.onCompletion(() -> { // 连接完成后清理缓存 emitterCache.remove(taskId); }); emitter.onTimeout(() -> { try { emitter.send(SseEmitter.event().name("timeout").data("任务执行超时")); emitter.complete(); } catch (IOException e) { e.printStackTrace(); } finally { emitterCache.remove(taskId); } });
4. 验证SSE消息格式与序列化
SSE有严格的消息格式要求,如果格式错误,不仅客户端收不到,服务端也可能抛出序列化异常:
- 发送消息时必须用
SseEmitter.event()构建正确的结构,包含data字段; - 确保要发送的结果对象是可JSON序列化的(比如添加
@JsonProperty注解,避免循环引用)。
5. 检查Spring异步配置是否生效
别忘了在Spring配置类上添加@EnableAsync注解,确保异步任务能正常启动。如果异步线程池配置不合理(比如线程数太少),也可能导致任务排队,延迟执行到Emitter超时。
客户端Angular的配合要点
虽然你说客户端代码已经写好,但还是提个醒:Angular的EventSource会自动重连,所以要在收到任务完成的消息后主动调用eventSource.close(),避免客户端重复建立无效连接,导致服务端发送到已失效的连接上报错。
举个简单的客户端示例:
startLongTask(): void { const eventSource = new EventSource('/api/tasks/start'); eventSource.addEventListener('taskComplete', (event) => { console.log('任务完成结果:', JSON.parse(event.data)); eventSource.close(); // 主动关闭连接 }); eventSource.addEventListener('error', (error) => { console.error('SSE连接错误:', error); eventSource.close(); }); }
按照上面的步骤排查,应该能解决服务端发送响应时报错的问题。
内容的提问来源于stack exchange,提问作者Amit
相关产品推荐
相关产品推荐

