由 Direct buffer memory 引发的附件下载优化方案

文章探讨了JavaNIO在处理大文件时可能导致的DirectBuffer内存溢出问题,提出了改用普通IO操作以避免该问题。对于附件下载,文章指出了服务端需先完全传输文件给前端的问题,并介绍了利用nginx的X-Accel-Redirect特性进行本地下载优化以及针对Minio存储的文件如何安全下载的方法,包括获取和使用临时访问令牌。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

  1. 本地上传大文件内存溢出 Direct buffer memory
  2. 附件下载服务端传流给前端需要将流缓存完毕才可以下载,导致大文件下载系统崩溃

一、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 可以看到接口响应的大小一直在上升,直到整个文件传输完毕浏览器才会响应。
F12 可以看到
我们可以看到互联网下载都是在浏览器下载管理器下载,那么我们如何实现?
在这里插入图片描述
实际上本地下载我们通过nginx代理后的地址可以直接访问文件地址进行下载,满足上述要求。但是这样的操作会导致附件下载绕过后端直接请求,无法防止盗链。minio上传无法通过nginx实现(不考虑下载到服务器nginx代理,这样不合理)。

2.2 解决方案

2.2.1 本地下载

nginx + 重定向X-Accel-Redirect
这个功能允许你在后端处理权限,日志或任何你想干的,Nginx提供内容服务给终端用户从重定向后的路径,因此可以释放后端去处理其他请求(直接由Nginx提供IO,而不是后端服务)。这个功能类似 X-Sendfile 。

具体步骤篇幅太长请移步链接

2.2.1 minio下载

  1. minio管理界面可以截取到下载路径,模拟他的下载即可

  2. 连接地址发现需要传入minio的登录token,下一步想办法获取token

  3. 高版本支持多用户,可创建临时用户获取token(8.4.3)

  4. 高版本管理界面下载路径变了,【minio ip port】+/api/v1/buckets/+【bucket】+/objects/download?prefix=【Base64.*encode(附件路径)】+\&token=* + *token*

  5. 直接访问上一步的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;
    }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr-Wanter

感谢大佬

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值