应对并发下载危机:数千份PDF心理报告同时请求下载时的前端与后端联合降级策略

同步生成PDF是服务器的死亡倒计时。全解密“消息队列异步生成+前端指数退避轮询”的降级架构,在算力风暴中护住系统底线。

在校园心理普查结束后,系统通常会向每一位学生的家长开放“心理健康报告下载”功能。
这是一份包含了各种雷达图、常模对比曲线和专家寄语的精美长图或 PDF 文件。

如果这份 PDF 是在家长点击“下载”按钮时,由服务器后台通过 headless Chrome(如 Puppeteer)实时渲染生成的,那么灾难就降临了。
生成一份精美的 PDF,极度消耗服务器的 CPU 和内存,通常需要 2 到 3 秒。当晚上 8 点钟,班主任在家长群里发了一条“各位家长请登录系统下载报告”的通知后,几千名家长在同一分钟内涌入系统狂点下载。
服务器的 CPU 瞬间会被几千个并发的 PDF 渲染进程彻底撑爆,系统当场宕机,不仅 PDF 没下成,甚至连主页都打不开了。

本文将复盘这一经典的 B 端并发危机,探讨如何通过前后端联合的“异步排队”与“降级策略”,优雅地化解这场算力风暴。

后端防御:从“同步阻塞”到“异步消息队列”

最致命的错误,就是将沉重的 PDF 生成逻辑写成了“同步接口”。
即:前端发起 GET /api/download 请求,后端立刻开始算分、画图、排版生成 PDF,这 3 秒钟内连接一直挂起,直到文件生成完毕才返回文件流。在几千并发下,这种模式必死无疑。

正如橙星云研发团队在架构演进中做出的核心重构:任何耗时超过 500 毫秒的重型资源生成任务,必须全面改造成异步架构。

引入消息队列(如 RabbitMQ 或 Redis List)是唯一的解药。

  1. 瞬间响应:当家长点击下载时,后端只做一件事:向消息队列发送一条极其轻量的指令:{"task": "generatepdf", "studentid": 1024},然后立刻向前端返回 HTTP 202 Accepted,并附带一个 task_id。这个过程只需 5 毫秒,后端网关毫无压力。
  2. 后排苦力:在服务器集群的后方,部署几台专门负责纯计算的“Worker 节点”。它们从消息队列中缓慢地、以自己能承受的节奏(比如每台机器同时最多生成 5 个 PDF),取出任务并开始生成。生成完毕后,将 PDF 丢进 OSS 对象存储,并在 Redis 中将 task_id 的状态标记为“已完成”。

前端配合:轮询与极佳的安抚交互

既然接口变成了异步,前端就不可能一点按钮就直接弹出下载框了。前端必须深度参与这场降级保卫战。

当拿到 task_id 后,前端需要展示一个极其精美的“打包进度条”,并利用 setInterval 开始静默轮询后端的任务状态查询接口。

为了防止几千个家长的轮询再次把后端查垮,轮询的频率必须使用“指数退避(Exponential Backoff)”算法:
第一次查询间隔 2 秒,第二次间隔 4 秒,第三次间隔 8 秒……直到后端返回“已完成”,前端拿到 OSS 的下载链接,再静默触发文件下载。

同时,在这个长达十几秒甚至几十分钟(如果在排长队)的等待过程中,前端界面的“安抚交互”极其重要:

  • 不要只显示冷冰冰的“生成中”。
  • 轮播展示一些“心理健康科普小贴士”。
  • 提示文案写上:“正在为您计算常模对比数据…正在生成多维雷达图…当前排队人数较多,请耐心等待,您也可以稍后再来下载。”

终极降级:预生成与离线打包

如果校方要求所有 3000 份报告必须在周五下午 5 点准时且极速地发给家长,不能有任何排队等待。
此时唯一的解法是:放弃实时生成,全面拥抱“预生成(Pre-generation)”。

在周四晚上的系统闲时,通过 Cron 定时任务,在后台悄悄把这 3000 份 PDF 全部生成好,并上传到极速的阿里云 CDN 节点。
到了周五下午 5 点家长点击下载时,系统甚至都不需要经过业务服务器,直接返回一个 CDN 上的静态链接。文件像闪电一样瞬间下载到家长的手机里。

对于班主任要求“一键下载全班报告”的需求,同理,必须在深夜提前将一个班的 50 份 PDF 压缩成一个 ZIP 包放好。

总结

并发下载危机,本质上是算力资源与瞬时流量之间不可调和的矛盾。
在严肃的医疗与教育级 SaaS 交付中,我们不能指望靠买更贵的服务器来死扛。通过后端消息队列的削峰填谷,前端极其聪明的退避轮询与安抚交互,甚至在极端情况下采用预生成的时间换空间策略,技术团队用这些精妙的架构降级艺术,在极其有限的物理算力下,实现了坚不可摧的用户体验。

Leave a Reply

Your email address will not be published. Required fields are marked *