1. SSE技术背景与应用场景解析
Server-Sent Events(SSE)是一种基于HTTP协议的服务器推送技术,它允许服务端主动向客户端发送数据更新。与WebSocket相比,SSE采用简单的文本协议,天然支持断线重连和事件ID追踪,特别适合金融行情看板、物流跟踪、实时监控等需要单向数据流的场景。
在Spring Boot中实现SSE主要依赖SseEmitter类,它本质上是对HTTP长连接的封装。当客户端发起SSE连接时,服务端会保持这个连接开放(默认30秒超时),通过分块传输编码(chunked transfer encoding)持续推送数据。这种机制避免了轮询带来的性能损耗,也比WebSocket更轻量级。
实际项目中我们发现:SSE连接数受限于服务器线程池配置,每个连接都会占用一个Tomcat工作线程。对于高并发场景需要调整server.tomcat.max-threads参数或考虑反应式编程方案。
2. Spring Boot SSE实现核心组件
2.1 服务端关键接口设计
典型的SSE控制器包含三个核心方法:
java复制@RestController
@RequestMapping("/sse")
public class SseController {
private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
@GetMapping("/subscribe")
public SseEmitter subscribe(@RequestParam String clientId) {
SseEmitter emitter = new SseEmitter(180_000L); // 3分钟超时
emitters.put(clientId, emitter);
emitter.onCompletion(() -> emitters.remove(clientId));
emitter.onTimeout(() -> emitters.remove(clientId));
return emitter;
}
public void pushData(String clientId, Object data) {
SseEmitter emitter = emitters.get(clientId);
if (emitter != null) {
try {
emitter.send(SseEmitter.event()
.id(UUID.randomUUID().toString())
.data(data, MediaType.APPLICATION_JSON));
} catch (IOException e) {
emitter.completeWithError(e);
}
}
}
}
关键参数说明:
SseEmitter(timeout):设置连接超时时间(毫秒),建议根据业务特点调整event().id():事件ID用于客户端断线重连时的数据同步data()方法支持String/JSON/XML多种格式
2.2 客户端实现要点
前端通过EventSource API建立连接:
javascript复制const eventSource = new EventSource('/sse/subscribe?clientId=123');
eventSource.onmessage = (e) => {
const data = JSON.parse(e.data);
// 更新DOM...
};
eventSource.onerror = () => {
// 处理连接中断
};
实测中发现:Chrome浏览器默认限制每个域名最多6个SSE连接,需要合理设计客户端ID生成策略。
3. 生产环境优化方案
3.1 性能调优参数
在application.properties中配置:
properties复制# 增大Tomcat工作线程池
server.tomcat.max-threads=200
server.tomcat.max-connections=10000
# 调整异步请求超时
spring.mvc.async.request-timeout=300000
3.2 集群部署方案
多节点部署时需要解决两个问题:
- 客户端连接与节点绑定问题
- 跨节点消息广播问题
推荐方案:
java复制// 使用Redis Pub/Sub实现跨节点通知
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory factory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
container.addMessageListener((message, pattern) -> {
String clientId = new String(message.getBody());
// 本地节点检查是否存在对应emitter
}, new ChannelTopic("sse.notify"));
return container;
}
4. 常见问题排查指南
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 连接立即断开 | 客户端未正确解析响应头 | 检查服务端produces="text/event-stream" |
| 数据推送延迟 | 服务端阻塞操作占用线程 | 改用@Async异步处理 |
| 内存泄漏 | 未清理失效的emitter | 添加onCompletion/onTimeout回调 |
| 集群消息丢失 | 网络分区导致Redis通知失败 | 添加重试机制+本地缓存 |
5. 高级功能扩展
5.1 二进制数据传输
虽然SSE规范仅支持文本,但可以通过Base64编码传输二进制:
java复制emitter.send(SseEmitter.event()
.data(Base64.getEncoder().encodeToString(binaryData))
.comment("BINARY_DATA"));
5.2 自定义事件类型
分类型处理不同业务事件:
java复制// 服务端
emitter.send(SseEmitter.event()
.name("stockUpdate")
.data(stockData));
// 客户端
eventSource.addEventListener("stockUpdate", (e) => {
// 处理特定事件类型
});
6. 监控与运维建议
- 指标收集:
java复制@Bean
public MeterRegistryCustomizer<MeterRegistry> metrics() {
return registry -> registry.gauge("sse.connections", emitters, Map::size);
}
- 连接保活策略:
java复制// 每30秒发送心跳事件
scheduledExecutor.scheduleAtFixedRate(() -> {
emitters.forEach((id, emitter) -> {
emitter.send(SseEmitter.event().comment("heartbeat"));
});
}, 30, 30, TimeUnit.SECONDS);
在电商实时订单跟踪系统中,我们采用上述方案成功支撑了5万+并发连接。关键经验是:合理设置超时时间(不宜过长)、实现客户端自动重连逻辑、对非活跃连接及时清理。对于更高并发的场景,建议尝试Spring WebFlux的SSE实现方案。