You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jakarta EE如何检测SSE客户端断开并及时清理服务端资源

SSE失效连接清理方案(适配OpenLiberty + RESTEasy环境)

核心问题

在Jakarta EE服务端对接Web端、Java客户端搭建SSE通信通道时,通常会在内存中维护已连接客户端的SseEventSink实例列表实现主动推送,但默认配置下缺少可靠的客户端断连检测机制,会导致失效连接长期占用服务端资源。
当前典型实现代码如下:

@ApplicationScoped
@Path("/sse")
public class SseController {

    private Sse sse;
    private List<SseEventSink> sinks = new CopyOnWriteArrayList<>();

    @GET
    @Produces(SERVER_SENT_EVENTS)
    public void onSse(@Context Sse sse, @Context SseEventSink sink) {
        this.sse = sse;
        this.sinks.add(sink);
    }
}

默认实现存在的已知问题:

  • 客户端主动断开、网络闪断、进程崩溃后,服务端最长15分钟无法感知断连
  • 仅当向失效连接连续发送3次消息触发发送异常时才会清理资源,实时性差、无效资源占用高

可落地方案

1. 注册连接关闭回调,实现断连即时清理

RESTEasy作为OpenLiberty的SSE实现组件,其SseEventSink实现类原生支持关闭事件回调,不需要等待发送失败即可感知连接关闭:无论是客户端主动断开、网络中断还是连接超时,回调都会第一时间触发,直接在回调中移除对应失效sink即可。
修改连接建立逻辑:

@GET
@Produces(SERVER_SENT_EVENTS)
public void onSse(@Context Sse sse, @Context SseEventSink sink) {
    sinks.add(sink);
    // 连接建立后发送首条注释帧确认连接状态,同时注册关闭回调
    sink.send(sse.newEventBuilder().comment("connected").build())
        .whenComplete((result, ex) -> {
            // 仅对RESTEasy实现的sink注册关闭监听,OpenLiberty全量支持版本均适配
            if (sink instanceof ResteasySseEventSink resteasySink) {
                resteasySink.onClose(() -> sinks.remove(sink));
            }
        });
}

注意:原代码中将请求上下文注入的Sse实例存储为类变量存在线程安全问题,@ApplicationScoped生命周期的Bean会被多请求并发访问,类变量会被反复覆盖,建议每次推送时通过注入获取Sse实例,或在连接建立时为每个sink单独绑定事件构造器。

2. 推送时增加状态校验,做兜底清理

关闭回调不能覆盖100%的异常场景(比如服务端重启、回调注册失败),需要在每次批量推送消息时增加二次校验,彻底避免资源泄漏:

public void broadcast(String messageContent) {
    OutboundSseEvent pushEvent = sse.newEvent("message", messageContent);
    Iterator<SseEventSink> sinkIterator = sinks.iterator();
    while (sinkIterator.hasNext()) {
        SseEventSink currentSink = sinkIterator.next();
        // 直接跳过已标记关闭的连接
        if (currentSink.isClosed()) {
            sinkIterator.remove();
            continue;
        }
        // 捕获发送过程中的异常,移除发送失败的失效连接
        try {
            currentSink.send(pushEvent);
        } catch (Exception e) {
            sinkIterator.remove();
        }
    }
}

3. 调整服务端超时配置,清理僵死连接

OpenLiberty默认的HTTP连接超时配置对SSE长连接适配较差,需要在server.xml中调整SSE专属的空闲超时参数,强制无响应的僵死连接触发关闭回调:

<httpEndpoint httpPort="9080" httpsPort="9443" id="defaultHttpEndpoint">
    <!-- TCP层读超时设置为5分钟 -->
    <tcpOptions soTimeout="300000" />
    <!-- SSE连接空闲超时设置为5分钟,无数据传输自动关闭连接 -->
    <sse idleTimeout="300000" />
</httpEndpoint>

如果业务对实时性要求高,可以把超时时间调整到1-2分钟,配合定期发送心跳注释帧(比如每30秒发一条:心跳帧),可以把断连感知延迟控制在1分钟以内。

关键注意事项

  • 遍历CopyOnWriteArrayList删除元素时必须使用迭代器的remove方法,禁止在for-each循环中直接删除,避免抛出并发修改异常
  • 不要依赖客户端侧发送的断开请求:网络闪断、客户端强杀进程的场景下,服务端无法收到应用层的断开通知,必须依赖TCP层断连感知+超时机制兜底
  • 心跳帧不要携带业务数据,用SSE规范规定的注释帧(以:开头)即可,不会触发客户端的业务消息回调

内容的提问来源于stack exchange,提问作者DanielM

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 11:21:18