SSE在Spring Boot中如何实现实时消息推送?

来源:Redis教程作者:刘卫东头衔:网络博主
导读:本期聚焦于刘卫东创作的《SSE在Spring Boot中如何实现实时消息推送?》,敬请观看详情。SSE(Server-Sent Events)是一种基于HTTP协议的服务器单向推送技术,浏览器通过一个长连接就能持续接收服务器发来的事件流数据。相比WebSocket,SSE实现更简单、无需额外协议支持,天然适合消息通知、进度回调、股票行情等场景。本文将围绕Spring Boot环境讲解SSE的核心原理与用法,包括SseEmitter的基本操作、心跳保活与断线重连机制、多客户端连接管理以及常见踩坑点的解决方案,同时对比SSE与WebSocket的适用边界,帮助你选对技术方案。

实时消息推送是很多业务系统的刚需,比如站内信通知、订单状态变更提醒、后台任务进度反馈等。说到推送,大部分人第一反应是WebSocket,但如果你的场景只是服务器向浏览器单向推送数据,SSE(Server-Sent Events)其实是更轻量的选择。它基于普通HTTP协议,不需要升级协议、不需要额外的依赖,Spring Boot原生就支持,几行代码就能跑起来。

SSE在Spring Boot中如何实现实时消息推送?

SSE的基本原理是什么

SSE的本质是一个保持打开状态的HTTP长连接。浏览器端通过EventSource对象发起请求,服务器端不关闭响应流,而是按照固定的文本格式分批写入数据。这个格式由W3C规范定义,每条消息由若干行组成,以空行结尾,例如:

event: message
data: {"orderId":1001,"status":"已发货"}
id: 20250101001

其中event表示事件类型,data是消息体,id用于断线重连时告知服务器上次收到的位置。浏览器收到消息后,会根据event名称触发对应的事件回调。整个过程只有一个单向通道:服务器写,客户端读,客户端不能通过这条连接向服务器发数据。

与轮询相比,SSE避免了大量无效请求;与WebSocket相比,SSE走的是标准HTTP/1.1或HTTP/2,经过代理、网关、负载均衡时兼容性更好,而且自带断线自动重连机制(EventSource默认会在连接断开后自动重连),这一点连原生WebSocket都不具备。

Spring Boot中如何用SseEmitter实现推送

Spring MVC从4.2版本开始提供了SseEmitter类,专门用于SSE场景。控制器方法返回一个SseEmitter对象,Spring会保持响应流打开,你在其他线程中调用send()方法即可向客户端推送消息。下面是一个完整的基础示例:

@RestController
public class NoticeController {

    // 保存所有在线的客户端连接
    private static final List<SseEmitter> EMITTERS = new CopyOnWriteArrayList<>();

    @GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter subscribe() {
        // 超时时间设为0表示永不超时,生产环境建议设置合理值
        SseEmitter emitter = new SseEmitter(0L);
        EMITTERS.add(emitter);

        emitter.onCompletion(() -> EMITTERS.remove(emitter));
        emitter.onTimeout(() -> EMITTERS.remove(emitter));
        emitter.onError(e -> EMITTERS.remove(emitter));

        // 发送一条欢迎消息,确认连接建立
        try {
            emitter.send(SseEmitter.event()
                    .name("connected")
                    .data("连接成功"));
        } catch (IOException e) {
            EMITTERS.remove(emitter);
        }
        return emitter;
    }

    // 模拟业务触发推送
    @PostMapping("/notify")
    public String notify(@RequestParam String message) {
        for (SseEmitter emitter : EMITTERS) {
            try {
                emitter.send(SseEmitter.event()
                        .name("notice")
                        .data(message));
            } catch (IOException e) {
                EMITTERS.remove(emitter);
            }
        }
        return "推送完成";
    }
}

有几个细节值得注意。第一,produces必须设置为text/event-stream,这是SSE规范要求的MIME类型,否则浏览器不会按事件流解析。第二,建议分别注册onCompletiononTimeoutonError三个回调,及时把失效的连接从容器中移除,否则连接对象会不断堆积,最终导致内存泄漏。第三,保存连接的集合要使用CopyOnWriteArrayList这类并发安全容器,因为推送往往发生在其他线程。

前端接入也非常简单,原生JavaScript就够了,不需要任何第三方库:

const source = new EventSource('/subscribe');

// 监听指定名称的事件,对应后端的 event().name("connected")
source.addEventListener('connected', function(e) {
    console.log('连接建立:', e.data);
});

source.addEventListener('notice', function(e) {
    console.log('收到通知:', e.data);
    // 这里可以做弹窗、角标提醒等UI处理
});

source.onerror = function(e) {
    console.log('连接异常,浏览器将自动重连');
};

生产环境必须处理的几个问题

心跳保活

长连接最大的敌人是中间网络设备的超时切断。Nginx默认对60秒内没有数据的连接会断开,一些云网关的超时时间甚至更短。解决办法是服务器定时发送一条注释行作为心跳。SSE规范规定以冒号开头的行是注释,浏览器会忽略它,但数据流本身刷新了连接的活跃时间:

@Scheduled(fixedRate = 25000)
public void heartbeat() {
    for (SseEmitter emitter : EMITTERS) {
        try {
            // 发送注释行作为心跳,浏览器端不会触发事件
            emitter.send(SseEmitter.event().comment("ping"));
        } catch (IOException e) {
            EMITTERS.remove(emitter);
        }
    }
}

心跳间隔建议设置为中间件超时时间的一半,比如网关30秒超时,心跳就设为15秒左右。别忘了在启动类上加@EnableScheduling注解开启定时任务。

异步线程与超时控制

SseEmitter默认依赖Servlet容器的异步支持,Spring Boot内嵌Tomcat已经开启。但要小心一点:如果Controller方法在Servlet请求线程中创建emitter后立即返回,而后续send操作发生在业务线程,这本身就是SSE的正常用法,不需要你自己开线程去阻塞。另外,设置超时时间时不要一味使用0(永不超时),长时间无交互的死连接会占用服务器资源,生产环境一般设置为30分钟到1小时,配合客户端自动重连即可。

如果项目中同时使用了拦截器或过滤器做鉴权,注意SSE连接建立后鉴权信息不会自动刷新。token过期后连接可能还挂着,业务上需要在推送时校验用户身份是否仍然有效,失效就主动调用emitter.complete()关闭连接。

网关与反向代理配置

SSE经过Nginx时需要关闭缓冲,否则Nginx会把事件流缓冲到一定量才转发,导致消息延迟严重。配置方式如下:

location /subscribe {
    proxy_pass http://backend;
    # 关闭代理缓冲,保证事件实时透传
    proxy_buffering off;
    # 长连接读写超时加大
    proxy_read_timeout 300s;
    # 对HTTP/1.1保持长连接
    proxy_http_version 1.1;
    proxy_set_header Connection '';
}

SSE与WebSocket该怎么选

两者的核心区别在通信方向。SSE是服务器到客户端的单向流,WebSocket是全双工。如果你的业务只需要服务器推(通知、行情、日志 tail、进度条),SSE在开发成本、运维复杂度、兼容性上全面占优:不需要引入额外的依赖和协议栈,普通HTTP基础设施直接可用,浏览器端还免费获得自动重连能力。

反过来,如果业务存在高频双向交互,比如聊天室、协同编辑、多人游戏,WebSocket才是正确的选择。另外需要注意浏览器对HTTP/1.1同域名的连接数限制(通常是6个),如果同一页面要开多条SSE连接,建议启用HTTP/2,它对同域多路复用没有这个瓶颈。

总的来说,SSE是“够用就好”的典型代表。在Spring Boot中配合SseEmitter,加上心跳保活和连接管理这两板斧,就能稳定支撑绝大多数单向推送场景,值得在实际项目中优先考虑。

SSEServer-Sent EventsSpring Boot实时推送修改时间:2026-09-04 05:02:37

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260904/50027.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。