月底最怕的不是对不平,是不知道对到哪儿了:进度的可见性
摘要:月底最消耗人的不是有差异,是不知道进度。一个「跑着呢」的答复能让五个人在群里等一小时。进度要能被说清,必须先拆成四个数字:总数、已处理、成功、失败。这篇讲清五类异步任务的量级与失败方式、进度四件套、重试的边界,以及月底真正该盯的几个数。 关键词:异步任务、任务进度、对账进度可见、失败重试、月结节奏
一、那句「还在跑」
月初一号,早上九点。财务经理问:对账跑完没有?操作的人回:还在跑。
两小时后同一个问题,同样的回答。下午,老板问什么时候能出结果,没人答得上来。晚上八点,系统里一行红字:任务失败。
这一天没有产生任何业务价值,但所有人的情绪都被消耗了。而且这不是技术故障——即使任务最终成功,这一天也一样难熬,因为中间的过程不可见。
所以「进度可见」不是体验优化,是月结节奏的基础设施。它要回答四个问题:一共多少、处理了多少、成功多少、失败多少。少一个都不够。
二、五类任务,各有各的脾气
对账链路上的任务不是一类东西,混在一起谈进度必然谈不清。
| 任务类型 | 输入特征 | 失败后的正确处置 |
|---|---|---|
| 导入 | 单个账单文件,几十行到几十万行 | 单文件失败不影响其他文件,可单独重试 |
| 解析 | 一批原始行,按脚本规则转换 | 脚本错误整批停,修规则后重跑 |
| 对账 | 计划内的订单与原始行匹配 | 单计划失败可重跑,结果幂等 |
| 分摊 | 费用聚合成分摊行 | 失败回退到未分摊,重跑先撤旧 |
| 转换 | 对账结果生成单据 | 单据已存在则跳过,不重复生成 |
量级也不同。小额账单可能几秒就解析完,大促期间单文件几十万行的账单,光导入就可能要按分钟计。这里不写精确数字,因为不同环境差异极大——重要的是让每一类任务都能被单独观测,而不是给所有任务套一个统一的超时时间。
把五类任务混成一个「对账任务」,你会遇到一个典型问题:不知道是导入慢、解析慢,还是卡在转换推送。分开之后,卡在哪一类一目了然。
三、进度四件套
一个可用的进度,必须同时给出四个数。
总数:这一批要处理多少行、多少订单、多少单据。没有总数,「处理了 500」就没有意义。
已处理:当前推进到哪儿。这个数在批次内应单调递增,如果回退,说明有重试或重跑。
成功:处理完成且结果正确的数量。
失败:处理了但失败的数量,并带上失败原因分类。
四个数放在一起才能形成判断:总数 5000、已处理 5000、成功 4990、失败 10,这就是「快跑完了,有 10 条要处理」;总数 5000、已处理 800、成功 800、失败 0,这是「还在跑,暂时正常」。只给「进度 16%」,两种判断都会猜错。
进度还需要一个时间维度:已经跑了多久、按当前速度预计还要多久。预计时间不必精确,但要有,否则没人知道该等还是该干别的。
这里有个容易被忽略的设计要求:进度必须可持久化。页面刷新、服务重启之后,进度还在。只存在内存里的进度条,在真正的异步任务里等于没有。
四件套看起来简单,难的是四件套要如实。有些系统为了「看起来好看」,把已处理报成成功,或者把失败的行从总数里剔掉——数字漂亮了,月底对不上时连定位都无从下手。可见性的价值在于它敢报坏消息。轻易云把任务状态与处理结果分开记录,正是为了让这四个数各自独立、互不覆盖:成功数不会随着进度推进而虚增,失败数也不会被平均值抹掉。
四、失败不能无限重试
看起来「失败就自动重试」很省事,实际是月结事故的主要来源。
原因有三。第一,很多失败是确定性的——脚本报错、字段缺失、规则不匹配,重试一万次结果一样,只是把资源耗光。第二,重试会掩盖问题。一个反复重试最后成功的任务,在日志里看起来是「有惊无险」,实际上是某个前提条件恰好被满足了,下次未必。第三,无限重试会和限速打架,把对方的接口额度打满,连累正常任务。
我们的边界是这样:只对可重试错误重试,比如网络超时、对方临时不可用、资源争用。次数有限(三到五次),间隔指数增长。重试用尽就判失败,进人工队列,并带上最后一次的真实错误。
与之配套的是整批语义:一批任务里如果有失败,整批的最终状态是「部分失败」而不是「成功」。不要用「成功率 99%」这种口径把失败藏起来——月底这 1% 就是全部争议所在。
还有一条:整批超时要能整体回滚。一个跑到一半被强制中断的批次,如果留下半套结果,下一批重跑会在半套结果上继续算,错误会累积。回滚的粒度要按业务对象定,不能只回滚任务状态。
这一层的实现方式各家不同,但目标一致:任何一次中断都不留下半套结果。轻易云的做法是让执行与回滚都以业务对象为单位,而不是以批次为单位整体扫尾——批次是人的概念,业务对象才是数据的概念,按批次扫尾经常扫不干净。
五、月底该盯哪几个数
可见性最终要落到「盯什么」上。月底真正需要盯的不是百分比,是几个具体数字。
待处理差异数:已知差异里还没归因的条数。这个数字决定还要不要有人加班。
未完成计划数:本应在本期完成的计划里,还有几个没跑完。它比整体进度更能暴露遗漏。
失败任务数:当前处于失败状态、还没处理的任务数量,以及最老的失败任务已经挂了多久。
待推送单据数:对账结果已经生成单据但还没推到 ERP 的数量。
这四个数字的共同点是:它们都是绝对数,都能直接对应到「有没有事要做」。百分比好看但不行动,绝对数会逼人行动。
顺带说一句架构上的取舍:任务状态和任务结果要分开存。状态是轻的、高频更新的;结果是重的、一次写入的。混在一起会导致查询进度时把大字段也读出来,越到月底越慢。做成独立一层之后,「看一眼进度」这个动作在月底高峰期也不会变慢,这是它该有的样子。
六、可见性不是给人看的
有人会问:进度条做那么细,谁看?
答案是:它主要不是给人看的,是给判断用的。系统靠进度判断该不该重试、该不该判超时;人靠进度判断该不该等、该不该叫人。两边用同一套数字,才不会出现「机器说在跑、人说没动静」的分歧。
进度的最高形态是有结论。跑完之后,一句话能说清:本批 5000 行,成功 4990,失败 10,失败原因是 8 条金额为 0、2 条订单号缺失,已进入人工队列。这句话比十个进度条都有用。
要产出这句话,失败原因就必须结构化:不是一段自由文本,而是有限枚举加上原始行号。枚举让统计成为可能,行号让定位成为可能。只报「失败 10 条」而不报原因,等于把排查工作又还给了财务。
七、快任务与慢任务不是一回事
把任务拆成五类之后,还有一个更容易被忽略的维度:快慢。
快任务是秒级完成的,比如校验一批行的格式、查一个订单的当前状态。这类任务用同步接口就好,放进队列反而增加延迟和排查成本——你会为了等一个 200 毫秒的操作,去盯一个异步任务的状态。
慢任务是分钟级以上的,比如导入几十万行、跑一遍全量对账、批量推送几千张单据。必须异步,且必须可观测。判断标准不是「代码好不好写」,而是「用户愿不愿意盯着屏幕等」。
真正麻烦的是中间地带:一个任务平时几十秒完成,大促时变成几分钟。这时候如果按同步设计,大促当天会大面积超时;如果按异步设计,日常又要多一次轮询。可行的做法是按数据量动态决定:小批量走同步并直接返回结果,超过阈值转异步并给出任务号。
拆批之后,进度要按父任务聚合。子任务各自报四件套,父任务把子任务的「总数 / 已处理 / 成功 / 失败」累加起来,而不是取平均或取最慢的那个。这里最容易出错的是重复计数:同一个子任务重试后成功,如果父任务把两次都累加,总数会超过真实值。解决办法是给子任务一个幂等键,父任务只按幂等键的最新状态计数。
举个实际例子:一张三十万行的账单,拆成三十个子批次,每个一万行。前端显示的不是「第 7 个子批次跑完」,而是「总数 30 万、已处理 17 万、成功 16.98 万、失败 200」。失败的那 200 行能直接点开看是哪几行、什么原因,而不是等整批跑完再去翻日志。这个差别看起来只是展示方式,实际决定了月初那天财务是在等结果,还是在处理结果。
这也是为什么「进度」和「报告」要分开设计:进度回答「还有多久」,报告回答「哪里不对」。混成一个页面,两边都会做得很勉强。
八、给财务和实施的四个动作
第一,把「对账任务」拆成五类任务分别观测。混在一起,永远定位不到卡点。
第二,进度只报四件套:总数、已处理、成功、失败。只报百分比的进度条不叫可见。
第三,重试只覆盖可重试错误,次数封顶,用尽进人工队列。无限重试是给月结埋雷。
第四,把「有结论」当验收标准:任务结束后必须能一句话说清结果,说不清就是设计没做完。这句话也该成为月结复盘的固定提问——如果没有这句话,那次加班就没有沉淀出任何可复用的东西。