采集任务之间的依赖:别用 cron 错峰赌运气
任务一多,「先采什么、再采什么」的顺序问题就来了:先采搜索、再采详情;先采完数据、再出报表。第一版我用 cron 错峰硬排——搜索 6 点跑、详情 7 点跑、报表 8 点跑,赌「6 点的任务肯定 1 小时能跑完」。直到有一天搜索任务因为重试拖到 7 点半,详情任务 7 点照跑不误,拿到一份全空的 feature_id 列表。教训:任务顺序应该由「依赖」决定,不是由「时间」决定。
依赖的本质:数据依赖,不是时间依赖
任务 B 依赖任务 A,本质是「B 需要 A 的产物」:
- 触发条件:A 完成(且成功)后 B 才能启动
- 产物可用:A 的产出已经落库/落盘,B 能读得到
- 失败传播:A 失败了,B 怎么办——这是最容易被忽视的一条
把「6 点、7 点、8 点」换成「A→B→C」的依赖图,一切就显式化了。
实现层级(从简到繁)
层级一:脚本链。 A && B && C——顺序有了,但失败定位、跳过、部分重跑全靠人判断。适合两三个任务的阶段。
层级二:状态表 + 前置列表(推荐的中间形态)。
CREATE TABLE jobs (
job_id text PRIMARY KEY,
deps text[], -- 前置任务列表
status text NOT NULL, -- pending/running/done/failed
started_at timestamptz,
finished_at timestamptz
);
调度逻辑就一个循环:
def next_ready(jobs):
"""所有前置 done、自己 pending 的任务"""
return [
j for j in jobs
if j.status == "pending"
and all(get_status(jobs, d) == "done" for d in j.deps)
]
def run_all(jobs):
while True:
ready = next_ready(jobs)
if not ready:
break
for job in ready:
try:
do_work(job)
set_status(job, "done")
except Exception:
set_status(job, "failed") # 依赖它的任务不会启动
这张表本身就回答了「谁等谁、谁失败、从哪续」——排查时看一眼状态列就够。
层级三:成熟调度器(Airflow/Prefect/Dagster)。 任务多、重试策略复杂、要可视化依赖图的时候再上。多数采集系统停在层级二就够。
失败传播的三个策略(按依赖性质选)
A 失败后,B 的处理不是只有「停」一种:
- 硬依赖(必须重跑):详情任务依赖搜索的 feature_id——A 失败,B 必须等 A 恢复后重跑
- 可降级:报表可以先用昨天的数据——A 失败,B 用旧产物 + 标注「数据截至昨日」
- 可跳过:日报的某可选环节——A 失败,B 跳过并告警
别一刀切——一刀切「全停」会让噪音变大,一刀切「不管」会让脏数据悄悄流通。
产物版本化
依赖的准确对象是「A 的某次运行产物」,不是「A 最近的状态」。落库时带 run_id:
-- 搜索任务产物
SELECT * FROM search_results WHERE run_id = ?;
这样重跑 A 不会影响「已经消费了旧产物的 B」——重跑语义(A 重跑后 B 要不要跟进)是明确的设计决定,而不是事后惊讶。
踩坑记录
坑 1:用 cron 错峰代替依赖。 任务慢一次就错乱,错乱的还是数据链路。依赖显式化是第一原则。
坑 2:依赖记录在人的脑子里。 新同事不知道「为什么详情要等搜索」。依赖写进状态表/文档。
坑 3:失败传播一刀切。 全停=噪音,不管=脏数据。按业务性质选策略。
坑 4:重跑语义没定义。 「重跑 A 之后 B 要不要也重跑」没人定,各跑各的。写进任务文档。
坑 5:产物没版本。 B 处理的是 A 的旧产物还浑然不知。带 run_id。
坑 6:循环依赖。 A 等 B、B 等 A,挂在那里永远不跑。依赖图加环检测(构建时校验)。
工程清单
- 数据依赖显式化:触发条件 + 产物可用 + 失败传播
- 状态表 + 前置列表,调度循环 20 行内解决
- 失败传播分类:硬依赖 / 可降级 / 可跳过
- 产物带 run_id,重跑语义写进文档
- 依赖图构建时做环检测
- 状态表本身是可观测性(谁等谁一眼可见)
任务依赖的本质,是把「顺序」从「编排在时钟里」搬到「建模在数据里」。依赖图的每一根箭头都在回答三个问题:B 何时能跑(A 完成了吗)、跑出来对不对(产物版本对吗)、A 挂了 B 怎么办(传播策略是什么)。答全这三问,任务再也不会「因为运气而错乱」。
任务产出与请求的关联可以靠接口响应里的 request_id(做产物版本与追踪的锚点),字段说明见 SerpBase 官方文档。你们的任务顺序是依赖还是错峰?评论区聊聊事故现场。

浙公网安备 33010602011771号