Spring Cloud Gateway MVC 使用SseEmitter时存在SSE连接泄漏问题
Spring Cloud Gateway MVC 使用SseEmitter时存在SSE连接泄漏问题
我最近在使用Spring Cloud Gateway MVC路由到内部API时遇到了一个头疼的问题:当后端API用SseEmitter实现Server-Sent-Events(SSE)长连接时,网关无法正确感知客户端断开的信号,导致后端的SseEmitter长连接和线程一直被占用,形成连接泄漏。而换成响应式的Spring Cloud Gateway就完全正常,这就很让人困惑了。
问题复现场景
我写了一个极简的复现示例,后端API的配置和核心代码如下:
后端API基础配置
server.port: 8781 spring.application.name: api
后端SSE接口实现
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.UUID; import java.time.Instant; import java.util.concurrent.Executors; import java.util.concurrent.ExecutorService; import com.fasterxml.jackson.annotation.JsonProperty; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class SseController { private static final Logger log = LoggerFactory.getLogger(SseController.class); public record EventDetails( @JsonProperty("eventId") UUID id, @JsonProperty("timestamp") Instant time, @JsonProperty("counter") int counter ) { } @GetMapping("/stream-sse-mvc") public SseEmitter streamSseMvc() { // 创建无超时的SseEmitter SseEmitter emitter = new SseEmitter(-1L); ExecutorService sseMvcExecutor = Executors.newSingleThreadExecutor(); // 注册生命周期回调,用于监控连接状态 emitter.onCompletion(() -> { log.info("Emitter has been closed"); sseMvcExecutor.shutdownNow(); }); emitter.onTimeout(() -> { log.info("Emitter has timed out"); emitter.complete(); }); emitter.onError(t -> { log.error("Emitter completed with error", t); emitter.completeWithError(t); }); // 启动线程每秒发送一条SSE事件 sseMvcExecutor.submit(() -> { try { for (int i = 0; true; i++) { final UUID id = UUID.randomUUID(); SseEmitter.SseEventBuilder event = SseEmitter.event() .data(new EventDetails(id, Instant.now(), i + 1)) .id(id.toString()) .name("sse event - mvc"); log.info("Sending event. [counter={}]", i); emitter.send(event); Thread.sleep(1000); } } catch (InterruptedException e) { log.info("Sending thread interrupted"); Thread.currentThread().interrupt(); } catch (Exception e) { log.error("Failed to send SSE event", e); emitter.completeWithError(e); } }); return emitter; } }
这个接口的逻辑很简单:创建一个无超时的SseEmitter,启动单独线程每秒向客户端发送一条事件,循环不会主动终止,全靠客户端断开时触发的回调来清理资源。
现象对比
直接调用后端接口:正常工作
当我直接用HTTP客户端(比如curl、Postman)调用这个SSE接口,然后主动断开连接时,后端的SseEmitter会立刻感知到:
- 触发
onError回调,打印错误日志 - 触发
onCompletion回调,关闭线程池 - 发送线程会因为
emitter.send()抛出异常而终止
对应的日志如下:
2025-11-25T17:19:41.671+01:00 INFO 168228 --- [api] [pool-2-thread-1] c.rbellini.api.controller.SseController : Sending event. [counter=0] 2025-11-25T17:19:42.740+01:00 INFO 168228 --- [api] [pool-2-thread-1] c.rbellini.api.controller.SseController : Sending event. [counter=1] ... 2025-11-25T17:19:52.762+01:00 INFO 168228 --- [api] [pool-2-thread-1] c.rbellini.api.controller.SseController : Sending event. [counter=11] 2025-11-25T17:19:52.765+01:00 ERROR 168228 --- [api] [nio-8781-exec-1] c.rbellini.api.controller.SseController : Emitter completed with error org.springframework.web.context.request.async.AsyncRequestNotUsableException: Servlet container error notification for disconnected client ... 2025-11-25T17:19:52.766+01:00 INFO 168228 --- [api] [pool-2-thread-1] c.rbellini.api.controller.SseController : Emitter has been closed
通过Gateway MVC调用:连接泄漏
但当我通过Spring Cloud Gateway MVC路由到这个接口,再断开客户端连接时,后端的SseEmitter完全没有任何反应:
- 既不触发
onError也不触发onCompletion回调 - 发送线程会一直无限循环发送事件,完全不知道客户端已经断开
- 后端的Tomcat连接和线程会一直被占用,直到手动重启服务
我的疑问与求助
我试过调整Gateway的各种超时配置(比如connect-timeout、response-timeout),但似乎都没起作用。现在想请教各位:
- 为什么Spring Cloud Gateway MVC没法把客户端断开的信号正确传递给后端的
SseEmitter? - 有没有什么配置或者代码改造的方式,能让Gateway MVC正确处理这种SSE长连接的关闭?
- 是不是Gateway MVC在处理异步Servlet请求(比如
SseEmitter这类)时,有什么特殊的机制我没配置对?
如果有朋友遇到过类似问题,或者对Spring Cloud Gateway MVC的异步请求处理逻辑熟悉的,麻烦给点思路,谢谢!




