[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/design/zero-copy-file-download.html
背景
现有报表系统异步导出报表,生成的报表会上传到对象存储中,因为安全问题,用户不能直接上对象存储系统中下载文件,需要通过报表服务代劳,因为不需要对其做修改,只需做转发,所以这里考虑使用零拷贝技术进行优化
现有做法
- 把文件数据『下载』下来,然后把对应的文件返回给客户端
- 数据经过两次拷贝,一次是从对象存储下载到报表服务,一次是从报表服务下载到客户端
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | @Controllerpublic class DownloadController {
 @RequestMapping(value = "/download", method = RequestMethod.GET)
 public ResponseEntity<byte[]> downloadFile() throws IOException {
 HttpClient client = HttpClientBuilder.create().build();
 HttpGet request = new HttpGet("http://xxxx/xxxx.zip");
 HttpResponse response = client.execute(request);
 byte[] zipContent = EntityUtils.toByteArray(response.getEntity());
 HttpHeaders headers = new HttpHeaders();
 headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
 headers.setContentDispositionFormData("attachment", "xxx.zip");
 headers.setContentLength(zipContent.length);
 return new ResponseEntity<byte[]>(zipContent, headers, HttpStatus.OK);
 }
 }
 
 
 | 
零拷贝优化
- 通过ZeroCopyInputStreamWrapper把文件数据直接『转发』给客户端
- 数据只经过2次拷贝,不需要经过报表服务,直接从对象存储转发给客户端
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | @Controllerpublic class DownloadController {
 @RequestMapping(value = "/download", method = RequestMethod.GET)
 public ResponseEntity<InputStreamResource> downloadFile() throws IOException {
 CloseableHttpClient client = HttpClients.createDefault();
 HttpGet request = new HttpGet("http://xxxx/xxxx.zip");
 HttpResponse response = client.execute(request);
 HttpEntity entity = response.getEntity();
 HttpHeaders headers = new HttpHeaders();
 headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
 headers.setContentDispositionFormData("attachment", "xxx.zip");
 headers.setContentLength(entity.getContentLength());
 InputStream stream = new ZeroCopyInputStreamWrapper(entity.getContent());
 return new ResponseEntity<InputStreamResource>(new InputStreamResource(stream), headers, HttpStatus.OK);
 }
 }
 
 | 
PS
PS: 使用ZeroCopyInputStreamWrapper将HttpEntity的输入流包装成ZeroCopyInputStream,实际上是将HttpEntity的输入流传递给了ZeroCopyInputStream,而不是将响应数据读取到Java的用户内存中
原理
在ZeroCopyInputStreamWrapper中,它通过使用Java NIO的Direct ByteBuffer和底层的通道来实现DMA的零拷贝操作
 数据直接从通道读取到Direct ByteBuffer中,跳过了CPU拷贝的过程,实现了高效的数据传输
其他