惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

爱范儿
爱范儿
博客园_首页
U
Unit 42
Apple Machine Learning Research
Apple Machine Learning Research
云风的 BLOG
云风的 BLOG
MongoDB | Blog
MongoDB | Blog
美团技术团队
H
Help Net Security
G
Google Developers Blog
B
Blog RSS Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
aimingoo的专栏
aimingoo的专栏
Google DeepMind News
Google DeepMind News
J
Java Code Geeks
M
MIT News - Artificial intelligence
腾讯CDC
IT之家
IT之家
Vercel News
Vercel News
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
Last Week in AI
Last Week in AI
I
InfoQ
博客园 - 司徒正美
A
About on SuperTechFans

Aimee's Blog

秒杀怎么防机器人:验证码之外的几道防线 防薅羊毛怎么做:号码风控、设备指纹与分层策略 接口限流怎么选:固定窗口、滑动窗口、令牌桶 图片处理怎么做:异步处理与 CDN 实时参数 大文件上传怎么设计:分片、断点续传、秒传 报表可重跑的设计:快照、水位线与幂等 UV 统计为什么有误差:精确计数与近似计数怎么选 大数据量导出怎么做:流式写入与异步任务 排行榜的两类需求,难度差了一个数量级 内容审核怎么选:先审后发,还是先发后审 实时热榜怎么设计:Redis ZSet 与热度分值 互关状态怎么保持同步:双向关系的维护与缓存失效 并发点赞的计数设计:原子操作与最终对账 删了父评论,子评论怎么处理——先想清楚用户看到的是什么 定时消息任务触发了两次 消息量大,写扩散还是读扩散——接到这个需求先问清楚规模 同一条消息推送了三次 活动结束了,用户还在收短信 接到注销需求,先问两个问题 微服务与服务拆分:何时拆、怎么拆 异步与事件驱动架构:把协作从「打电话」改成「发消息」 高可用设计:怎么让系统尽量不宕机 可扩展性设计:怎么让系统加机器就能扛更多 缓存架构:多级缓存怎么搭 高并发三板斧:限流、熔断、降级 架构设计到底在设计什么 —— 从单体到微服务的演进 服务成本账:一个服务一个月烧多少钱 可观测性:线上出问题怎么查 API 设计:好接口长什么样 消息队列:为什么要 MQ,以及丢失、重复、顺序怎么破
批量打包下载怎么设计:流式 ZIP 与异步任务
Aimee · 2026-08-16 · via Aimee's Blog

批量打包下载怎么设计:流式 ZIP 与异步任务

批量打包下载的典型需求:用户勾选几百个文件,后端打成一个 ZIP 返回。

最直觉的实现是把文件全部读进内存再压缩:200 个文件平均 1MB,就是 200MB 堆内存——并发 10 个用户同时打包,2GB 内存光这一个接口就占满了。


流式 ZIP:边读边写,不在内存积累

response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=files.zip");

try (ZipOutputStream zos = new ZipOutputStream(
        new BufferedOutputStream(response.getOutputStream()))) {

    for (FileItem file : fileList) {
        zos.putNextEntry(new ZipEntry(file.getName()));
        // 从 OSS 流式读取,写入 zip 流
        try (InputStream in = ossClient.getObject(file.getOssKey()).getObjectContent()) {
            byte[] buf = new byte[8192];
            int len;
            while ((len = in.read(buf)) != -1) {
                zos.write(buf, 0, len);  // 每次只占 8KB 缓冲
            }
        }
        zos.closeEntry();
    }
}

整个过程内存里只有 8KB 的读缓冲区,无论多少个文件都不会因为文件总量撑内存。

流式输出的限制:不能设置 Content-Length(因为压缩后大小在写完前不知道),浏览器看不到下载进度条。这在大多数场景可以接受。


同步还是异步

场景方案
文件数量少(< 20 个),总大小 < 50MB同步流式输出
文件数量多或体积大异步任务:后台生成 ZIP,上传 OSS,发下载链接

异步方案的流程:

用户提交打包请求 → 创建 task,返回 taskId
          ↓(Worker 异步执行)
    从 OSS 流式读取每个文件 → 写入 ZipOutputStream → 目标是 OSS 的 PutObject 流
          ↓
    ZIP 文件上传完成,生成带过期时间的下载链接
          ↓
    通知用户(站内信 / App Push)→ 用户点击链接直接从 OSS 下载

Worker 里也要用流式写入,只是目标从 response.getOutputStream() 换成了 OSS 的 multipart upload 接口。


OSS 直链 vs 打包

如果文件本来就在 OSS 上,有时候不需要打包,直接给用户多个 OSS 直链更省事:

  • 200 个文件 → 生成 200 个带签名的临时 URL(有效期 1 小时)
  • 前端用 <a download> 或 JS 批量触发下载

浏览器对同时下载的文件数量有限制(通常 6 个),但逐个排队下载对用户透明,用户体验比等待 ZIP 生成快很多。

打包下载更适合:文件数量极多(几百上千)、用户需要在本地保持目录结构、文件需要二次处理不适合直链暴露。


大 ZIP 的清理

异步生成的 ZIP 文件放在 OSS 上,要设置过期时间(生命周期规则),比如 7 天自动删除,避免存储费用无限累积。下载链接的有效期和 ZIP 的存储期要对齐,否则文件删了链接还有效,用户点了报 404。