首页 > 解决方案 > Ktor - 处理大文件操作而不会发生内存泄漏

问题描述

我对后端开发很陌生。基本上,我想创建一个健壮且简单的应用程序,它将接受参数中的 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。 在此处输入图像描述

标签: filekotlinmemory-leaksbackendktor

解决方案


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

        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
            } }

推荐阅读