文章目录
- 前言
- 一、Direct buffer memory
- 1.1 原因分析
- 1.2 解决方案
- 二、附件下载
- 2.1 问题分析
- 2.2 解决方案
- 2.2.1 本地下载
- 2.2.1 minio下载
前言
- 本地上传大文件内存溢出 Direct buffer memory
- 附件下载服务端传流给前端需要将流缓存完毕才可以下载,导致大文件下载系统崩溃
一、Direct buffer memory
后端服务采用nio本地上传到服务器指定目录,通过nginx代理提供下载
1.1 原因分析
Java8出现了NIO,缓存,通道,选择器。在 写NIO程序的时候,经常使用ByteBuffer来读或者写入数据,这是一种基于通道(
Channel)与缓冲区( Buffer)的I/0方式.它可以使用 Native函数库直接分配堆外内存,然后通过一个存做在Java里面的
DirectByteBuffer对作为这块内存的引用进行操作。可以提高性能,因为避免了在Java堆和 Native堆中来回复制数据。
ByteBuffer. allocate(capability)第一种方式是分配java堆内存,属于GC管理范围,由于需要进行拷贝,所以比较慢。
ByteBuffer. allocteDirect (capability)第一种方式是分配操作系统的本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快。但如果不断分配本地内存,堆内存很少使用,那么java虚拟机就不需要进行GC,
DirectByteBuffer对象们就不会被回收,这个时候堆内存充足,但本地内存可能已经使用光了,在尝试分配本地内存就会出0ut0fMemory
Error,那程序就直接奔溃了。
linux 清理内存命令echo 1 > /proc/sys/vm/drop_caches
1.2 解决方案
抛弃nio,特别是对大数据流场景,这里改用普通io操作数据流(后续引发问题分析)
二、附件下载
2.1 问题分析
告别Direct buffer memory的后续问题
无论是本地下载还是minio下载都存在一个隐形问题。
前端blog请求后,要等服务端将整个附件流传输完毕后才可以下载,对大文件(600M)不友好,大概率卡死。
F12 可以看到接口响应的大小一直在上升,直到整个文件传输完毕浏览器才会响应。
我们可以看到互联网下载都是在浏览器下载管理器下载,那么我们如何实现?
实际上本地下载我们通过nginx代理后的地址可以直接访问文件地址进行下载,满足上述要求。但是这样的操作会导致附件下载绕过后端直接请求,无法防止盗链。minio上传无法通过nginx实现(不考虑下载到服务器nginx代理,这样不合理)。
2.2 解决方案
2.2.1 本地下载
nginx + 重定向X-Accel-Redirect
这个功能允许你在后端处理权限,日志或任何你想干的,Nginx提供内容服务给终端用户从重定向后的路径,因此可以释放后端去处理其他请求(直接由Nginx提供IO,而不是后端服务)。这个功能类似 X-Sendfile 。
具体步骤篇幅太长请移步链接
2.2.1 minio下载
-
minio管理界面可以截取到下载路径,模拟他的下载即可
-
连接地址发现需要传入minio的登录token,下一步想办法获取token
-
高版本支持多用户,可创建临时用户获取token(8.4.3)
-
高版本管理界面下载路径变了,
【minio ip port】+/api/v1/buckets/+【bucket】+/objects/download?prefix=【Base64.*encode(附件路径)】+\&token=* + *token*
-
直接访问上一步的url即可下载
https://github.com/minio/minio-java/tree/release/examples
public Credentials getCredentials() {
int durationSeconds = 360000;//秒
//创建签名对象
AssumeRoleProvider provider = new AssumeRoleProvider(
properties.getUrl(),
properties.getAccessKey(),
properties.getSecretKey(),
durationSeconds,//默认3600秒失效,设置小于这个就是3600,大于3600就实际值
"{\n" +
" \"Version\": \"2012-10-17\",\n" +
" \"Statement\": [\n" +
" {\n" +
" \"Effect\": \"Allow\",\n" +
" \"Action\": [\n" +
" \"s3:GetObject\",\n" +
" \"s3:GetBucketLocation\",\n" +
" \"s3:PutObject\"\n" +
" ],\n" +
" \"Resource\": [\n" +
" \"arn:aws:s3:::test/*\"\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}",
properties.getRegion(),
"arn:aws:s3:::*/*",
"anysession",
null,
null);
Credentials credentials = provider.fetch();
return credentials;
}
public String downloadByLink(HttpServletRequest request, HttpServletResponse response, String fileId) {
AttachmentPO po = attachmentService.findById(fileId);
Credentials credentials = minioTemplate.getCredentials();
String token = credentials.sessionToken();
//为了统一前端访问路径,直接查客户端请求Referer minio是前端ng代理的minio实际访问
String referer = request.getHeader("Referer");
String url = referer + "minio/api/v1/buckets/" + po.getClientId() + "/objects/download?prefix=" + Base64.encode(po.getPath()) + "&token=" + token;
return url;
}