后台任务:34 秒的同步导出与三次重复文件

导出平均三十四秒、网关三十秒就断:用户重试三次得到三份 1.4 MB 文件。改成受理四秒、后台十二秒完成后,每月二百一十次导出的等待从 1.98 小时降到十几分钟;本文把三类任务、两个并发槽与三次退避逐项算清,并给出八条验收动作。

连接断开不等于任务失败,但用户只会按失败处理。

一、网关三十秒断开,用户重试了三次

下午三点,林澈点下「导出全部任务」。进度条走到三十秒,网关返回 504;他再点一次,又是 504;第三次他换了浏览器,终于看到文件。晚上整理下载目录,他发现同一个项目有三份 1.4 MB 的 CSV,时间相隔一分半。

以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。

导出平均三十四秒,p95 四十八秒,而网关三十秒就断开。三十四已经大于三十,四十八是三十的 1.6 倍;平均水平的导出都等不到结果,更不用说尾部。

断开的只是连接,服务器上的导出没有停。用户看到 504,按失败处理;服务端把文件写完,按成功结束。两边对同一件事的判断不一致,重试就成了用户唯一能做的动作。

超时不是任务失败,只是等待的人先走了。 服务端还在写文件,客户端已经拿到 504;如果不把「受理」和「完成」分开,这个误解会一直存在。

重试次数等于重复文件数。 同步接口没有任务号,用户无法查询进度,只能再点一次;每点一次就多一份 1.4 MB,三份里有两份是白生成的。

用户不需要在连接上等文件。 他需要的是「已经收到」和「去哪里取」,前者四秒就够,后者可以晚十二秒。

那天晚上他数了一下三份文件的时间戳,分别是 15:00:07、15:01:39、15:03:12,间隔一分半左右,正好是网关断开、用户再点一次的时间。

二、同步与异步的分界

分界线不是「耗时超过几秒」,而是「用户是否需要在这个连接上等结果」。导出要把整张表读出来、拼成 CSV、写进存储再返回,这三件事都不需要用户盯着进度条。

维度同步导出异步导出
受理时间34 s(p95 48 s)4 s 返回任务号
完成时间请求内完成12 s 后台完成
网关 30 s连接被切断不受影响
失败可见性504,不知道文件是否生成状态页给出原因
重试用户手动,产生重复文件队列自动退避,一份文件
用户下一步只能等或再点一次可以关掉页面

同步接口把受理、执行、交付三件事挤进一个 HTTP 连接;异步只把受理留在连接里,执行交给队列,交付交给状态页或下载链接。连接的长度从三十四秒降到四秒,剩下三十秒的工作并没有消失,只是换了一个不占用户时间的地方。

改造前,导出是唯一让连接超过网关超时的接口;通知和缩略图都远在超时线以内,所以只需要改一个接口,不需要重写整个后端。

网关超时是硬约束,不是建议值。 三十秒不会因为导出「快好了」而延长;把任务塞进这个窗口,等于把失败率交给数据量决定。

失败可见性比失败本身更重要。 504 只告诉用户「这次没拿到」,不告诉他文件有没有生成;任务号加状态页才能回答「要不要再点一次」。

异步不是把三十四秒藏起来。 后台仍然要跑十二秒,服务器成本没有减少;它减少的是用户被连接拴住的时间,以及由此产生的重复文件。

三、三类任务、两个并发槽与三次退避

队列不是越复杂越好。这套系统只有三类任务、两个并发槽、三次退避;先把这三件事定下来,再谈用什么产品。

任务触发并发失败处理
导出用户点击与缩略图共享 2 个槽退避三次后进失败列表
缩略图上传附件同上退避重试,保留原图
通知状态变更同上,优先取空闲槽退避重试,合并同类通知

每月二百一十次导出,摊到三十天是每天七次;七次乘十二秒,一天只有八十四秒的后台工作。平均下来一个槽都用不满,两个槽是为了突发,不是为了日常。

任务类型决定优先级。 导出可以等,通知不能等;三类任务共用槽位时,短任务要能插到长任务前面,否则一次导出会把一串通知堵在后面。

并发决定互相阻塞。 两个槽里至少留一个给短任务;如果两个槽都被导出占满,用户改一个状态要等两个导出跑完,也就是 2 × 12 = 24 秒,才收到通知,这比导出慢更难受。

退避决定重试会不会打爆下游。 30 s、2 min、8 min,累计 630 秒,也就是十分钟半;第一次覆盖连接抖动,第二次覆盖一次重启,第三次覆盖一段维护窗口。

退避要加随机抖动。三个任务在同一秒失败,如果都按三十秒重试,会在同一秒再次撞上下游;抖动让它们错开,代价只是多等几秒。

状态要可见。 排队中、生成中、已完成、失败,四个状态写在任务表里,也写在状态页上;只写「处理中」等于没有状态,用户仍然不知道要不要等。

四、1.98 小时与 4.2 MB 重复

二百一十次导出,每次三十四秒,合计七千一百四十秒,也就是每月 1.98 小时。这不是工程师的工时,是用户被连接拴住的时间;它不会出现在工资单上,但会出现在「这个产品怎么这么慢」的评价里。

改成受理四秒后,每次少等三十秒;每月少等六千三百秒,也就是 1.75 小时。后台仍然要十二秒,但那十二秒不再占用用户的页面。

重复文件是三份乘 1.4 MB,合计 4.2 MB,其中 2.8 MB 是纯重复。单次 4.2 MB 不算大,贵的是用户要花时间判断哪一份最新;这个判断成本不会出现在存储账单里。

真正要记住的是:三份文件不是存储问题,是接口设计问题;只要用户拿不到任务号,他唯一能做的就是再点一次,第四份、第五份迟早会出现。

复算:
月度等待:210 次/月 × 34 s = 7,140 s ÷ 3,600 = 1.98 h/月
折算全年:1.98 × 12 = 23.8 h/年
日均等待:7,140 ÷ 30 = 238 s/日 ≈ 4.0 分钟/日
异步受理:4 s/次
每次少等:34 − 4 = 30 s
每月少等:30 × 210 = 6,300 s = 1.75 h/月
后台完成:12 s(不占用户等待)
重复文件:3 × 1.4 MB = 4.2 MB
纯重复:2 × 1.4 MB = 2.8 MB
退避累计:30 + 120 + 480 = 630 s = 10.5 分钟
日均任务:210 ÷ 30 = 7 次/日
日均后台耗时:7 × 12 = 84 s/日
投入:4 h
占全年工时:4 ÷ 1,920 = 0.21%
占性能预算:4 ÷ 40 = 10%

四小时里,两小时改接口与任务表,一小时配并发与退避,一小时做一次真实导出演练。前两小时决定用户能不能离开页面,后两小时决定失败时他能不能看懂。

先改受理,再改队列。 如果接口仍然同步执行,队列只是多了一层包装;受理时间不降,网关照样会断。

文件只留一份。 同名导出以任务号区分,任务成功后删掉旧副本或标记过期;用户不需要在三个几乎一样的文件名里猜。

十二秒是观测值,不是承诺。 数据量涨了它会变;状态页要显示实际耗时,而不是写死一个「约十秒」。

五、行动清单与复算验收

  • 导出接口改成受理即返回,响应体只含任务号与状态 URL。
  • 网关超时与任务超时分开配置,三十秒只负责受理。
  • 建任务表:任务号、类型、状态、创建、完成、文件地址、错误原因。
  • 三类任务共用两个并发槽,通知保留一个短任务通道。
  • 配置 30 s、2 min、8 min 三次退避,超过三次进失败列表。
  • 同名导出只保留一份文件,成功后删除旧副本或标记过期。
  • 状态页显示排队中、生成中、已完成、失败,失败给可读原因。
  • 做一次真实演练:点一次、关页面、回来看到一份 1.4 MB 文件。

验收标准:导出接口四秒内返回任务号、十二秒内生成一份文件;网关三十秒超时不再产生 504,也不再出现第二份同名文件。

清单的顺序就是实施顺序:先让连接变短,再让任务可见,最后才调并发与退避。反过来先调退避,用户仍然要在页面上等三十四秒,只是失败后重试得更整齐。

八条里前三条是止血,后五条是可持续。只做前三条,这次的三份文件不会再来;做完后五条,下一次数据量翻倍时才不用重新设计。

使用边界与实操验收

本文只讲响应时间与重复文件。 同一个任务被重复执行会不会重复扣款、重复发信,属于幂等与补偿的问题,见 后台任务与一致性:六个定时任务、幂等键与一次补偿 ;两篇的检查项不重叠。

任务量很小时不要上独立队列。 每天七次、每次十二秒,一天八十四秒,cron 加一张任务表就够;多一个队列产品,就是多一个要升级、要排查、要读文档的依赖。

不要在请求线程里等结果。 sleep 轮询再返回,连接仍然被占着,网关仍然会断;受理接口要立刻返回,完成状态由另一个请求查询。

导出文件不要长期放在应用服务器磁盘。 对象存储加过期删除更省心,也不会因为一次重启丢掉正在生成的文件;本文不讨论存储价格与厂商选型。

什么时候该停下来。 当你开始为队列写第二个调度器,或者为「万一任务丢了」再加一套对账系统时,停下来——一个人维护不了两套,第二套会在半年内失修。

关键参考与核验范围

以上链接于 2026-10-26 实测可公开访问;未核验版本、功能范围与政策更新。核验日期:2026-10-26。

范围上只讨论同步接口转异步后的受理时间、完成时间与重复文件,不讨论幂等键、补偿事务、队列产品选型与厂商计价。

系列导航

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「Indie」更多文章

  1. 开源项目验收:十项能力、四维评分与三种不该开源的项目
  2. 治理、fork 与退出:17 位贡献者、82% 的提交与一次改协议的代价
  3. 双许可与企业版:九家询价、两家成交与 4,800 美元