Ktor - 处理大文件操作而不会发生内存泄漏

Posted

技术标签:

【中文标题】Ktor - 处理大文件操作而不会发生内存泄漏【英文标题】:Ktor - Handle large file operations without memory leak 【发布时间】:2021-03-14 02:07:07 【问题描述】:

我对后端开发非常陌生。基本上,我想创建一个健壮且简单的应用程序,它将接受参数中的 zip 文件 URL,然后从 URL 下载 zip 文件,最后提取 zip 并返回其中的 bin 文件。注意:zip 文件的大小范围为 5MB 到 150MB。我已经尝试通过以下方式进行描述的操作。

package la.sample

import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.http.HttpStatusCode
import io.ktor.response.respond
import io.ktor.response.respondFile
import io.ktor.routing.get
import io.ktor.routing.routing
import java.io.*


fun Application.startServer() 
    routing 
        get("/get-bin") 

            //Gets the AWS Url from params
            val awsUrl = call.request.queryParameters.get("url") ?: "Error"

            // Download the zip file from the AWS URL
            val client = HttpClient()
            val bytes = client.get<ByteArray>(awsUrl)

            //Create a temp file on the server & write the zip file bytes into it.
            val file = File(".", "data.zip") 
            file.writeBytes(bytes) 
            
            //Call a method to unzip the file
            unzipAndReturnBinFile()?.let  
                call.respondFile(it) //respond with bin file
             ?: kotlin.run
                call.respond(HttpStatusCode.InternalServerError)
            
        
    



fun unzipAndReturnBinFile(): File? 

    var exitVal = 0

    //Command shell to unzip the file
    Runtime.getRuntime().exec("unzip bundle.zip -d data").let //command shell to unzip the zip file
        exitVal += it.waitFor()
    

    //Check if the command executed successfully 
    if (exitVal == 0) 

        var binFile: File? = null

        //check if the extracted files contain `bin`
        File("data").listFiles().forEach 

        if (it.name.contains(".bin")) 
            binFile = it
        
    

    //return bin or null otherwise
    return binFile
 else 
    throw Exception("Command Shell Execution failed.")


以上代码在本地机器上运行良好,与 Zip 文件大小无关。但是,当它部署到 AWS 时,如果 zip 或 bin 文件大于 100 MB,则代码会中断并给出java.lang.OutOfMemoryError 错误。如果有人可以向我建议一种在后端处理大文件操作的正确方法,并且能够处理 100 多个此类并发调用,我将非常感激。谢谢。

我的远程机器的 Java 堆大小约为 1 GB。

【问题讨论】:

OutOfMemoryError 不仅会发生内存泄漏,它可能真的意味着机器或 jvm 内存不足。你在JVM上有什么设置,机器有多少可用内存? @Alex.T 为 java 程序分配了大约 1 GB 的堆大小。 exec 命令应该在不同的进程上运行,除了进程,变量不应该使用堆上的任何内存吗?你能附上堆栈跟踪吗 【参考方案1】:

您的问题不在于解压过程

runtime exec 命令在不同的进程上运行,并且只使用分叉进程的堆上的最小大小来保存返回地址的指令。

导致 outOfMemory 的问题在这些行中

val bytes = client.get<ByteArray>(awsUrl)
val file = File(".", "data.zip") 
file.writeBytes(bytes) 

只需要 6 个大小为 150Mb 的并发请求即可完成所有堆大小。

而不是等待文件完全下载才将其保存到磁盘,您应该使用 Stream,然后每次下载大量数据时将其保存到磁盘然后以这种方式下载文件的完整大小永远不会同时在 RAM 中。

使用apachecommons-io,例如:

FileUtils.copyURLToFile(URL, File)

或者,如果您想更好地控制程序,请尝试使用 Ben Noland 答案

https://***.com/a/921408/4267015

【讨论】:

该死!正是他们造成了问题。根据您的回答,我更改了代码以接收多部分文件,并在获得卡盘后立即将它们写入另一个文件,而不是将它们存储在内存中。它就像魅力一样。非常感谢。【参考方案2】:

根据@Naor 的评论,我更新了代码以接受多部分文件,并在我得到每个小夹头(部分)后立即将它们写入另一个文件,而不会将整个数据存储在内存中。它解决了这个问题。下面是更新后的代码sn-p。

        val file = File(".", Constant.FILE_PATH)
        call.receiveMultipart().apply 
            forEachPart 
                if (it is PartData.FileItem) 
                    it.streamProvider().use  input ->
                        file.outputStream().buffered().use  output -> input.copyToSuspend(output) 
                    
                
                it.dispose
             

【讨论】:

以上是关于Ktor - 处理大文件操作而不会发生内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

vue在比较大的项目中容易出现内存泄漏的原因是啥?

基本的内存泄漏的解释

内存泄漏问题总结

Jvm内存泄漏

JVM系列之六:内存溢出内存泄漏 和 栈溢出

java内存泄漏与处理