1. 什么是SSE技术?
SSE(Server-Sent Events)是一种基于HTTP协议的服务器推送技术,它允许服务器单向地向客户端(通常是浏览器)发送实时数据更新。想象一下你正在看一场体育比赛的实时比分直播,比分变化时页面会自动刷新,这就是SSE的典型应用场景。
与WebSocket不同,SSE是单向通信——数据只能从服务器流向客户端。这种特性让它特别适合以下场景:
- 后台任务进度通知(比如文件上传进度)
- 实时数据监控面板(服务器性能指标、股票行情)
- 新闻推送或社交媒体更新
- 简单的聊天应用(只需要接收消息)
我在实际项目中使用SSE的经历很能说明问题。去年我们有个客户需要实时监控服务器集群状态,最初考虑用WebSocket,但发现客户端只需要接收数据,不需要发送复杂指令。改用SSE后,开发周期缩短了40%,而且运行更稳定。
2. SpringBoot中配置SSE
2.1 基础环境搭建
首先创建一个标准的SpringBoot项目,只需要添加最基本的web依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>这里有个小技巧:建议使用SpringBoot 2.2+版本,它对异步处理做了优化。我在使用2.1.8版本时遇到过连接不稳定的问题,升级后完美解决。
2.2 核心控制器实现
创建一个简单的控制器来处理SSE连接:
@RestController @RequestMapping("/sse") public class SseController { @GetMapping(path = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter connect() { SseEmitter emitter = new SseEmitter(30_000L); // 30秒超时 // 发送欢迎消息 try { emitter.send(SseEmitter.event() .name("welcome") .data("连接成功!当前时间:" + LocalTime.now())); } catch (IOException e) { emitter.completeWithError(e); } // 模拟定时发送数据 Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { try { emitter.send(SseEmitter.event() .data("心跳检测:" + LocalTime.now())); } catch (Exception e) { emitter.completeWithError(e); } }, 1, 1, TimeUnit.SECONDS); // 连接关闭处理 emitter.onCompletion(() -> System.out.println("连接关闭")); emitter.onTimeout(() -> System.out.println("连接超时")); return emitter; } }这段代码有几个关键点:
produces = MediaType.TEXT_EVENT_STREAM_VALUE声明这是一个SSE端点SseEmitter的超时时间根据业务需求设置- 使用单独的线程池发送数据,避免阻塞主线程
3. 前端集成EventSource
前端集成非常简单,现代浏览器都原生支持EventSource API:
<!DOCTYPE html> <html> <body> <div id="messages"></div> <script> const eventSource = new EventSource('/sse/connect'); eventSource.addEventListener('welcome', function(e) { console.log('欢迎消息:', e.data); document.getElementById('messages').innerHTML += e.data + '<br>'; }); eventSource.onmessage = function(e) { console.log('收到消息:', e.data); document.getElementById('messages').innerHTML += e.data + '<br>'; }; eventSource.onerror = function() { console.error('连接出错,5秒后重试...'); setTimeout(() => new EventSource('/sse/connect'), 5000); }; </script> </body> </html>实际项目中我发现几个有用的技巧:
- 为不同类型的事件添加不同的事件监听器(如上面的'welcome')
- 错误处理中实现自动重连
- 在页面卸载时记得关闭连接:
window.onbeforeunload = () => eventSource.close();
4. 高级功能实现
4.1 多客户端管理
实际项目中我们需要管理多个客户端连接。下面是一个增强版的实现:
@Service public class SseService { private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>(); public SseEmitter subscribe(String clientId) { SseEmitter emitter = new SseEmitter(60_000L); emitters.put(clientId, emitter); emitter.onCompletion(() -> emitters.remove(clientId)); emitter.onTimeout(() -> emitters.remove(clientId)); return emitter; } public void sendToClient(String clientId, Object data) { SseEmitter emitter = emitters.get(clientId); if (emitter != null) { try { emitter.send(data); } catch (IOException e) { emitter.completeWithError(e); } } } public void broadcast(Object data) { emitters.forEach((id, emitter) -> { try { emitter.send(data); } catch (IOException e) { emitter.completeWithError(e); } }); } }4.2 异常处理与重连机制
SSE连接可能因为网络问题中断,完善的异常处理很重要:
@ControllerAdvice public class SseExceptionHandler { @ExceptionHandler public void handleIOException(IOException e, HttpServletResponse response) { response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value()); // 记录日志 System.err.println("SSE连接异常: " + e.getMessage()); } }前端可以这样增强健壮性:
function connectSSE() { const es = new EventSource('/sse/connect'); es.onerror = () => { es.close(); setTimeout(connectSSE, 3000); // 3秒后重连 }; return es; } const eventSource = connectSSE();5. SSE与WebSocket的对比选择
SSE和WebSocket各有优劣,这里是我的经验总结:
| 特性 | SSE | WebSocket |
|---|---|---|
| 通信方向 | 单向(服务器→客户端) | 双向 |
| 协议 | HTTP | 独立协议 |
| 实现复杂度 | 简单 | 较复杂 |
| 浏览器支持 | 除IE外主流都支持 | 现代浏览器都支持 |
| 适用场景 | 实时通知、监控 | 聊天、协作编辑 |
选择建议:
- 只需要服务器推送:优先考虑SSE
- 需要双向交互:选择WebSocket
- 需要支持IE:考虑长轮询或WebSocket
6. 性能优化实战技巧
经过多个项目实践,我总结出这些优化经验:
连接管理:
- 设置合理的超时时间(30-120秒)
- 及时清理断开连接的Emitter
- 使用UUID作为客户端标识
数据格式优化:
- 使用JSON格式传输结构化数据
- 压缩大体积数据
- 避免频繁发送小数据(合并发送)
服务器配置:
# application.properties server.servlet.session.timeout=60s spring.mvc.async.request-timeout=60000监控指标:
@RestController public class SseMetricsController { @Autowired private SseService sseService; @GetMapping("/sse/stats") public Map<String, Object> getStats() { return Map.of( "activeConnections", sseService.getConnectionCount(), "lastBroadcastTime", sseService.getLastBroadcastTime() ); } }
7. 真实案例:任务进度通知系统
去年我们为某云平台开发的任务中心就使用了SSE。核心需求是:
- 用户提交长时间运行的任务(如数据导出)
- 实时显示任务进度
- 任务完成时通知用户
实现代码片段:
@PostMapping("/export") public ResponseEntity<String> startExport(@RequestBody ExportRequest request) { String taskId = UUID.randomUUID().toString(); // 启动异步任务 CompletableFuture.runAsync(() -> { SseEmitter emitter = sseService.subscribe(taskId); try { for (int i = 0; i <= 100; i += 10) { Thread.sleep(1000); emitter.send( new ProgressUpdate(i, "处理中...") ); } emitter.send(new ProgressUpdate(100, "完成")); emitter.complete(); } catch (Exception e) { emitter.completeWithError(e); } }); return ResponseEntity.ok(taskId); }前端通过taskId订阅专属进度通道:
const taskId = await startExport(); const es = new EventSource(`/sse/progress?taskId=${taskId}`); es.onmessage = e => { const progress = JSON.parse(e.data); updateProgressBar(progress.percent); if (progress.percent === 100) { es.close(); } };这个方案上线后,用户对实时进度反馈非常满意,客服咨询量下降了35%。