SpringBoot实战:利用SseEmitter构建轻量级实时数据推送服务
2026/7/15 21:58:54 网站建设 项目流程

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; } }

这段代码有几个关键点:

  1. produces = MediaType.TEXT_EVENT_STREAM_VALUE声明这是一个SSE端点
  2. SseEmitter的超时时间根据业务需求设置
  3. 使用单独的线程池发送数据,避免阻塞主线程

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>

实际项目中我发现几个有用的技巧:

  1. 为不同类型的事件添加不同的事件监听器(如上面的'welcome')
  2. 错误处理中实现自动重连
  3. 在页面卸载时记得关闭连接: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各有优劣,这里是我的经验总结:

特性SSEWebSocket
通信方向单向(服务器→客户端)双向
协议HTTP独立协议
实现复杂度简单较复杂
浏览器支持除IE外主流都支持现代浏览器都支持
适用场景实时通知、监控聊天、协作编辑

选择建议:

  • 只需要服务器推送:优先考虑SSE
  • 需要双向交互:选择WebSocket
  • 需要支持IE:考虑长轮询或WebSocket

6. 性能优化实战技巧

经过多个项目实践,我总结出这些优化经验:

  1. 连接管理

    • 设置合理的超时时间(30-120秒)
    • 及时清理断开连接的Emitter
    • 使用UUID作为客户端标识
  2. 数据格式优化

    • 使用JSON格式传输结构化数据
    • 压缩大体积数据
    • 避免频繁发送小数据(合并发送)
  3. 服务器配置

    # application.properties server.servlet.session.timeout=60s spring.mvc.async.request-timeout=60000
  4. 监控指标

    @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%。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询