采集任务也分急诊和体检:任务优先级的工程实践

有天早上排名日报迟到了。查原因:一个 10 万关键词的历史补采任务正在跑,把 worker 全占了,日报任务排在后面干等。「所有任务平等」就是问题本身——日报有人盯着等,补采早跑晚跑没人管。这篇记录采集任务怎么做优先级。

第一步:给任务分级

按「谁在等、等多久能接受」分三级:

级别 任务类型 新鲜度要求 例子
P0 急诊 告警、日报 分钟级 排名跌破告警、早报数据
P1 门诊 常规刷新 小时/天级 常规关键词采集
P2 体检 补采、归档、验证 天/周级 历史补采、数据校验

分级的依据不是「你觉得哪个重要」,是「谁在等这个数据」。有人等的进 P0,没人等的进 P2。

第二步:选实现方式

方式一:按级别分队列(推荐起步)

不同级别独立队列、独立 worker:

QUEUES = {"p0": Queue(), "p1": Queue(), "p2": Queue()}
# worker 分配:P0 两个、P1 三个、P2 一个

简单、隔离彻底——P2 跑爆不影响 P0。缺点是容量固定,利用率有浪费。

方式二:单队列 + 优先级字段

任务表加 priority,worker 认领时排序:

UPDATE tasks
SET status = 'running', started_at = now()
WHERE id = (
  SELECT id FROM tasks
  WHERE status = 'pending'
  ORDER BY priority DESC, created_at ASC
  LIMIT 1
  FOR UPDATE SKIP LOCKED
)
RETURNING *;

priority DESC 保证 P0 先出,created_at ASC 同级先来先服务,SKIP LOCKED 让多 worker 并发认领不打架(配合幂等篇的状态机用)。

方式三:容量保底 + 加权(更大规模)

P0 永远保底 30% 并发,其余级别分剩余容量。复杂度上来了,一般到方式二就够用。

关键问题:防饥饿

纯按优先级排,P2 可能永远排不上——P0/P1 源源不断。解法是老化(aging):等待越久,优先级越高。

ORDER BY
  priority + LEAST(EXTRACT(EPOCH FROM now() - created_at) / 3600, 5) DESC,
  created_at ASC

每小时给任务加 1 点优先级,最多加 5——补采等久了会插队,但封顶,翻不了天。再配上「P2 保留一个 worker」的保底,基本不会饿死。

第三步:和限流、成本配合

  • 优先级决定先花谁的额度:P0 先消耗限流配额,P1/P2 用剩下的
  • 成本按级分开统计:P0 花了多少、P2 花了多少
  • P2 单独设预算上限,别让补采悄悄吃光预算

成本分级的原料是每笔请求的计费记录——接口响应的 credits_charged 字段正好干这个,SerpBase 官方文档 的计费说明里写得很清楚。

踩坑记录

坑 1:所有任务都是 P0。 谁都觉得自己重要,优先级直接失效。定死策略:新任务默认 P2,升 P0 要写「谁在等、等多久」。

坑 2:忘了防饥饿。 补采任务排了一周没跑,最后还是业务来催。老化机制必加。

坑 3:只在提交端标了优先级,认领端没排序。 worker 还是随机取任务。认领 SQL 必须 ORDER BY priority。

坑 4:没有分级指标。 出事不知道是哪个级别堵了。按级别记录队列深度、等待时长。

工程清单

  1. 按「谁在等 + 等多久」分级
  2. 分队列或优先级字段 + 认领排序
  3. 老化防饥饿(带上限)
  4. 优先级决定限流额度和预算分配
  5. 按级别看队列指标;新任务默认最低级

优先级调度的本质,是把「什么先做」从「谁先提交谁先跑」换成「谁在等谁先跑」。急诊先看、体检排队、等久了升号——这套挂号逻辑,采集系统一样适用。

任务状态机加优先级排序的完整认领逻辑,可以对照接口每次请求带 request_id 的设计来做审计,字段说明见 SerpBase 官方文档。你们的采集任务分级了吗?评论区聊聊事故现场。

posted @ 2026-09-22 09:21  蜘蛛人  阅读(5)  评论(0)    收藏  举报