关于没有对生产者做校验的思考
- 调用方提交任务;
- 生产者接口立刻返回成功,并给出任务标识;
- 页面上先显示「已创建 / 排队中」;
- 过一段时间再看,任务变成失败,没有产物,只留下失败阶段和失败原因。
发布成功、返回成功、产出失败
接口成功和业务成功被拆成了两件事,中间隔着一整段异步流水线。
生产者几乎没有做、或没有做够校验
只要请求能落库、能入队,就当成功返回了。
真正能不能产出,全部丢给了消费者。
QA1:异步生产不是「请求进来立刻出结果」
完整链路分三段:
text
入口段(生产者)
请求 → 校验 → 落库 → 入队
↓
中间段(消费者)
领取任务 → 准备运行态 → 执行生产 → 回写结果
↓
出口段(生产者)
展示产物或失败原因;必要时再做后续处理
因此接口返回成功,语义上只应该表示:
任务已被生产者接受:参数过关、记录已落库、已进入队列。
它不表示:
消费者一定能产出结果。
如果入口校验缺位,上面这句话会进一步退化成:
请求格式勉强能写进数据库。
这才是「发布成功、产出失败」的结构根源。
QA2:当时缺的是哪一层校验?
生产者本应在落库前挡住「确定会失败」的任务。
缺了这一层之后,脏数据会顺利走完全部门面流程:
请求没被拦住,于是落库成功、入队成功、接口返回成功。
消费者真正执行时才发现:引用不存在、契约对不上、约束不满足、配置无法执行。
最后任务被标成失败,调用方才第一次看见「没有产物」。
这些失败在消费者侧都是确定性失败:同一份数据重试一万次,结果还是失败。
这些本不该进入队列。
QA3:为什么会「接口成功」被骗到?
异步任务有一个很容易被忽略的语义陷阱:
text
发布成功 ≠ 生产成功
返回成功 ≠ 产物存在
排队中 ≠ 最终可用
生产者如果只保证「能写库、能入队」,会带来几层误导:
-
前端误报成功 调用方看到「创建成功」,会以为事情已经在推进,甚至离开页面去干别的。真正的失败要等消费者跑完才出现。
-
问题被推迟,且换了一个责任面 入口本可以当场拒绝,并指出哪一项不合法。缺校验后,同样的问题变成任务详情里的失败阶段。排查要从接口日志转到任务记录、消费者日志、下游依赖,成本陡增。
-
队列和消费者被无效任务占用 确定性失败的任务仍然要:入队、抢占、读数据、执行、失败回写。一批脏任务就能把正常任务堵住。
-
重试救不了 失败任务往往允许重新入队。如果失败原因是入口本该拒绝的契约错误,重试只是把同样的失败再走一遍。
-
成功口径不统一 调用方、任务状态、产物是否存在、后续是否完成,四处「成功」含义不同。联调时最常见的对话就是:「接口明明成功了,为什么没有结果?」
一句话:入口不校验,等于把同步可拒绝的错误,伪装成了异步生产事故。
QA4:校验应该放在哪?
不是所有校验都属于生产者,也不是所有失败都能在入口预判。
1. 生产者保证「这份任务配得上进入流水线」
必填项缺失、取值不在约定范围、引用对象不存在、类型不匹配、配置自相矛盾、用已有元数据就能证伪的约束。
这些规则不依赖消费者运行时,用请求参数和已有数据就能判断。
能在门口用参数证伪的,就不要让它成为消费者的失败阶段。
2. 消费者保证「进入流水线后按契约生产或明确失败」
文件能否解码、资源能否下载、运行时计算结果是否成立、外部依赖是否可用、机器和网络是否正常。
这些规则依赖真实执行过程,生产者不该假装能算准。
正确的成功语义
以后看这条链路,只承认三种成功,且不能混用:
bash
1. 发布成功:生产者校验通过,任务已落库,已进入队列。
接口可以返回成功,但文案应是「任务已创建」,不是「结果已生成」。
2. 生产成功:消费者执行完成,并回写了可用产物。
这时才有结果可看。
3. 后续成功:产物被继续处理到最终可用状态。
生产成功不等于整条业务已经结束。
对应地,失败也要分层:
bash
发布失败: 接口直接拒绝,任务不存在。
生产失败: 任务存在,状态为失败,并带上失败阶段和原因。
后续失败: 产物在,但后续处理没有完成。
如果生产者不做校验,第一种「发布失败」会消失,所有问题都挤进第二种「生产失败」。