grpc 流式传输
下载各种文件(文本或二进制文件)是每个企业应用程序的生死攸关的事情。 PDF文档,附件,媒体,可执行文件,CSV,超大文件等。几乎每个应用程序迟早都必须提供某种形式的下载。 下载是通过HTTP来实现的,因此完全包含此协议并充分利用它很重要。 特别是在面向Internet的应用程序中,诸如缓存或用户体验之类的功能值得考虑。 本系列文章提供了实现各种下载服务器时可能要考虑的各个方面的列表。 请注意,我避免使用“ 最佳做法 ”一词,这些只是我认为有用的准则,但不一定总是适用。
最大的可伸缩性问题之一是在流传输之前将整个文件加载到内存中。 将完整文件加载到byte[]以便稍后从Spring MVC控制器返回它,这是无法预测的,并且无法缩放。 服务器将消耗的内存量与并发连接数乘以平均文件大小成线性关系,而您实际上并不想太依赖这些因素。 将文件的内容从服务器直接逐字节流传输到客户端(使用缓冲)非常容易,实际上有很多技术可以实现。 最简单的一种是手动复制字节:
@RequestMapping(method = GET)
public void download(OutputStream output) throws IOException {
try(final InputStream myFile = openFile()) {
IOUtils.copy(myFile, output);
}
}
您的InputStream甚至不必缓冲, IOUtils.copy()会解决这个问题。 但是,此实现相当底层,并且很难进行单元测试。 相反,我建议返回Resource :
@RestController
@RequestMapping("/download")
public class DownloadController {
private final FileStorage storage;
@Autowired
public DownloadController(FileStorage storage) {
this.storage = storage;
}
@RequestMapping(method = GET, value = "/{uuid}")
public Resource download(@PathVariable UUID uuid) {
return storage
.findFile(uuid)
.map(this::prepareResponse)
.orElseGet(this::notFound);
}
private Resource prepareResponse(FilePointer filePointer) {
final InputStream inputStream = filePointer.open();
return new InputStreamResource(inputStream);
}
private Resource notFound() {
throw new NotFoundException();
}
}
@ResponseStatus(value= HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException {
}
创建了两个抽象以使Spring控制器与文件存储机制脱钩。 FilePointer是一个文件描述符,与该文件的获取位置FilePointer 。 目前,我们使用一种方法:
public interface FilePointer {
InputStream open();
//more to come
}
open()允许读取实际文件,无论它来自何处(文件系统,数据库BLOB,Amazon S3等)。我们将逐步扩展FilePointer以支持更多高级功能,例如文件大小和MIME类型。 查找和创建FilePointer的过程由FileStorage抽象控制:
public interface FileStorage {
Optional<FilePointer> findFile(UUID uuid);
}
流式传输使我们能够处理数百个并发请求,而不会显着影响内存和GC( IOUtils仅分配了一个小缓冲区)。 顺便说一句,我正在使用UUID来识别文件,而不是名称或其他形式的序列号。 这使得猜测单个资源名称变得更加困难,因此更加安全(晦涩)。 下一篇文章将对此进行更多介绍。 有了此基本设置,我们可以可靠地为大量并发连接提供服务,而对内存的影响最小。 请记住,Spring框架中的许多组件和其他库(例如Servlet过滤器)可能会在返回完整响应之前对其进行缓冲。 因此,进行集成测试以下载巨大的文件(以数十个GiB格式)并确保应用程序不会崩溃非常重要。
编写下载服务器
- 第一部分:始终流式传输,永远不要完全保留在内存中
- 第二部分:标头:Last-Modified,ETag和If-None-Match
- 第三部分:标头:内容长度和范围
- 第四部分:有效地执行HEAD操作
- 第五部分:油门下载速度
- 第六部分:描述您发送的内容(内容类型等)
- 这些文章中开发的示例应用程序可在GitHub上找到。
翻译自: https://www.javacodegeeks.com/2015/06/writing-a-download-server-part-i-always-stream-never-keep-fully-in-memory.html
grpc 流式传输