Java使用azure-storage-blob12.8.0实现Blob文件压缩回传的低内存问题咨询
核心实现逻辑
你要的低内存高速实现完全可以基于你现有的管道流思路实现,核心是两个异步线程分别处理「下载+打包」和「分块并行上传」,全程没有全量数据加载到内存,也不需要本地磁盘缓存。
第一步 修复现有打包线程的内存问题
你现有代码里用ByteArrayOutputStream把整个Blob下载到内存是严重错误,大文件会直接OOM,直接用缓冲区流拷贝即可,不需要缓存整个Blob内容:
// 初始化管道的时候建议调大缓冲区,减少IO阻塞,这里设为2MB final PipedOutputStream pipedOutputStream = new PipedOutputStream(); final PipedInputStream pipedInputStream = new PipedInputStream(pipedOutputStream, 1024 * 1024 * 2); // ... 保留你原有变量定义 CompletableFuture<Void> zipCreationFuture = CompletableFuture.runAsync(() -> { LoggerHelper.logInfo(logger, "开始异步打包文件"); try (ZipOutputStream zipOutputStream = new ZipOutputStream(pipedOutputStream)) { // 这里如果文件很多,你可以用线程池并行下载多个Blob,注意ZipOutputStream不是线程安全,下载后要顺序写入 for (String fileName : filesToZip) { try { BlobClient blobClient = objectStoreService.getBlobContainerClient().getBlobClient(fileName); LoggerHelper.logInfo(logger, "添加文件到压缩包: " + fileName); zipOutputStream.putNextEntry(new ZipEntry(fileName)); // 用8KB缓冲区流拷贝,不缓存整个文件 byte[] buffer = new byte[8192]; int read; try (InputStream blobIs = blobClient.openInputStream()) { while ((read = blobIs.read(buffer)) != -1) { zipOutputStream.write(buffer, 0, read); } } zipOutputStream.closeEntry(); } catch (SdkClientException e) { LoggerHelper.logExceptionWithMessage(logger, this.getClass().getName(), e); LoggerHelper.logError(logger, "下载Blob失败: " + fileName); throw new RuntimeException("打包失败", e); } } zipOutputStream.finish(); LoggerHelper.logInfo(logger, "压缩包打包完成"); } catch (IOException e) { LoggerHelper.logExceptionWithMessage(logger, this.getClass().getName(), e); LoggerHelper.logError(logger, "压缩包生成失败"); throw new RuntimeException("打包失败", e); } });
第二步 实现管道输入流的并行上传逻辑
用Azure SDK自带的并行上传能力,直接传入PipedInputStream,SDK内部会自动分块并行上传,不需要你自己实现分块逻辑:
CompletableFuture<Void> uploadFuture = CompletableFuture.runAsync(() -> { LoggerHelper.logInfo(logger, "开始上传压缩包: " + zipFileName); BlobClient zipBlobClient = objectStoreService.getBlobContainerClient().getBlobClient(zipFileName); // 配置并行上传参数,根据你的服务器带宽调整 ParallelTransferOptions transferOptions = new ParallelTransferOptions() .setBlockSizeLong(4 * 1024 * 1024L) // 单个块大小4MB .setMaxConcurrency(8) // 并行上传线程数 .setProgressReceiver(bytesTransferred -> LoggerHelper.logInfo(logger, "已上传字节数: " + bytesTransferred)); // 上传时长度传null,SDK会自动处理未知长度的流 zipBlobClient.upload(pipedInputStream, null, true, transferOptions, null, null, Context.NONE); LoggerHelper.logInfo(logger, "压缩包上传完成"); }).exceptionally(e -> { // 上传失败时主动中断打包线程,避免资源泄漏 zipCreationFuture.cancel(true); LoggerHelper.logExceptionWithMessage(logger, this.getClass().getName(), e); LoggerHelper.logError(logger, "压缩包上传失败"); throw new RuntimeException("上传失败", e); });
第三步 等待两个任务完成,处理最终结果
// 等两个异步任务都完成 CompletableFuture.allOf(zipCreationFuture, uploadFuture).join(); // 后续你自己的业务逻辑
性能优化建议
- 如果待打包的文件数量很多,可以把打包线程里的循环改成并发下载,控制并发数在4-8之间,下载完的流按顺序写入ZipOutputStream即可,进一步提升打包速度
- 并行上传参数
setMaxConcurrency可以根据你的服务器出口带宽调整,带宽够的话可以调到16,块大小可以改成8MB,进一步提升上传速度 - 如果你的文件普遍大于100MB,建议把管道缓冲区调到4MB以上,减少读写阻塞概率
内容的提问来源于stack exchange,提问作者Atharva
相关产品推荐
相关产品推荐

