SpringBoot使用ResponseBodyEmitter处理流式日志和进度条
作者:码猿技术专栏
自从 ChatGPT 火了之后,带动了大批量的流式输出的使用。
在流式输出火的那段时间,不少爱钻研技术的小伙伴们,都学习并上手了 SSE 异步处理。我那时也写了一篇文章,今天我们换一种更简单的方式,来实现流式输出。
它就是 ResponseBodyEmitter。它并不是一个新技术,早在 Spring Framework 4.2 版本中就被引入了。直到最近我们需要做一个滚动的日志输出功能,才了解到它。
作用
相比 SSE 技术,ResponseBodyEmitter 更简单。它常用于处理异步的 HTTP 响应,允许逐步发送数据到客户端,而不是一次性发送所有内容
。它适用于需要长时间处理或流式传输的场景。
需要注意的是,ResponseBodyEmitter
只是一个接口。
使用场景
- 长轮询:服务器在有数据时立即响应,否则保持连接开放。
- 服务器推送事件 (SSE):服务器可以持续向客户端推送事件。
- 流式传输:逐步发送大量数据,如文件下载或实时数据流。
- 异步处理:处理耗时任务时,逐步返回结果,避免客户端长时间等待。
业务举例
进度条、实时聊天、股票价格更新、系统日志流、AI 的流式输出等。
实时日志流
实战演练:实现一个简单的实时日志流。
为了让这个概念更加具体,我们来实现一个简单的实时日志流功能。假设你有一个应用程序,需要实时查看服务器的日志,以便快速定位和解决问题。
创建控制器
首先,我们在 Spring Boot 应用中创建一个控制器,使用 ResponseBodyEmitter
来实现实时日志流。
import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter; @RestController @RequestMapping("/api/log") public class LogController { @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public ResponseBodyEmitter streamLogs() { ResponseBodyEmitter emitter = new ResponseBodyEmitter(); // 异步处理数据并发送 new Thread(() -> { try { while (true) { String logEntry = getLatestLogEntry(); if (logEntry != null) { emitter.send(logEntry); } Thread.sleep(1000); // 每秒检查一次 } } catch (Exception e) { emitter.completeWithError(e); } }).start(); return emitter; } private String getLatestLogEntry() { // 模拟从日志文件中获取最新日志条目 return "2025-02-12 12:00:00 - INFO: User logged in successfully."; } }
运行效果
当你运行这个应用程序并访问 /api/log/stream
路径时,你会看到一个实时更新的日志流。每秒钟,服务器会向客户端推送一条新的日志条目,客户端会将其显示在页面上。
ResponseBodyEmitter 的核心方法
send(Object data)
:向客户端发送数据,可以多次调用。complete()
:结束响应流,表示数据发送完毕。onTimeout(Runnable callback)
:设置超时回调函数。onCompletion(Runnable callback)
:设置完成回调函数。
ResponseBodyEmitter 工作原理
- 服务端异步生成响应数据:任务执行时,调用 send() 方法将数据推送至客户端。
- 分块传输:数据以 HTTP 的**分块编码(Chunked Encoding)**方式传输,不会提前设置
Content-Length
,而是分段发送数据块。 - 连接生命周期:通过
complete()
或completeWithError()
控制连接的关闭。
注意事项
支持的客户端:大多数浏览器和 HTTP 客户端库支持分块传输,但某些老旧的客户端可能不支持。
超时设置:为了避免长连接占用资源,可以为ResponseBodyEmitter
设置超时时间:
emitter.onTimeout(() -> emitter.complete());
线程安全:ResponseBodyEmitter 的 send() 方法是线程安全的,但需要注意控制任务线程的生命周期。
连接关闭:需要确保任务结束时调用 complete() 或 completeWithError(),否则可能导致资源泄露。
与 Streaming 和 SSE 的对比
- Streaming:直接通过 OutputStream 向客户端写入数据,灵活性高,但需手动处理流的关闭。
- Server-Sent Events (SSE):基于 text/event-stream,适用于服务端事件推送,客户端需支持 SSE。
- ResponseBodyEmitter:更通用,适用于任何支持 HTTP 的客户端,且易于与 Spring 集成。
类似 AI 这样的响应式的、流式输出,相比 SSE 而言,ResponseBodyEmitter 是 Spring 提供的轻量级流式传输解决方案,同时 http 协议兼容性更好。
小结
ResponseBodyEmitter 是 Spring 提供的轻量级流式传输解决方案,能有效提升高并发和实时性场景的用户体验。通过 ResponseBodyEmitter
,我们可以轻松实现服务器向客户端的实时数据推送。无论是进度条、实时聊天、股票价格更新还是系统日志流,ResponseBodyEmitter
都能帮助我们构建更加动态和互动的应用程序。
到此这篇关于SpringBoot使用ResponseBodyEmitter处理流式日志和进度条的文章就介绍到这了,更多相关SpringBoot ResponseBodyEmitter处理日志内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!