跳转至

共享队列架构

类型:wiki(知识沉淀) 描述:v0.10.0 后的核心调度模型 — 多任务共用一个调度器、Tab/worker 是全局资源 最后更新:2026-05-26(v0.10.14) 相关源码src/entrypoints/background/batch-controller.tssrc/entrypoints/background/task-manager.tssrc/utils/engine-manager.ts 相关 rulesdocs/rules/settings-change-pre-check.md

是什么

v0.10.0 起,所有用户任务共用一个全局调度器: - 任务 = 用户在「任务页」创建的(关键词 × 城市 × 国家组合),生成 URL 列表 - 调度器 = 后台 service worker 里的 pumpScheduler / manageQueue - 资源 = 浏览器 Tab、HTTP fetch worker - 队列 = 全局,所有任务的 URL 进同一池子

[Task A: 100 URL]──┐
[Task B:   1 URL]──┼─→ 共享队列 ─→ Worker 池(N 槽)─→ Tab / Fetch
[Task C:  50 URL]──┘                    ↑
                                   N = maxConcurrentTasks
                                       / deepScrapeConcurrency
                                       / pagerConcurrency

为什么这么设计

v0.9.x(老版本)的死法

每个 task 自带独立 BatchState,N 个调度器在共享窗口里互相抢焦点

真实事故(v0.9.40 dentist 任务): - 两个任务同时跑,第二个开起来后 0 进度持续 53 分钟 - 根因:每个 batch 的 openTabInShared 都用 active: true 开 tab - 新 tab 抢前台 → 上一个 tab 被推到后台 → Chromium 后台节流(timer ≥1s, RAF 暂停) - content script 永远初始化不完 → 永远不发 'batch-tab-ready' → tab 永远不会 promote - setTimeout(60s 超时) 也可能死于 SW 重启

用户洞察(v0.10.0 启动原因):

"多个任务不应该是共用一个序列吗?"

完全正确。调度对象(窗口、tab)是共享的,调度者却被切成 N 个独立实例,必然撞车。

调度算法

地图抓取(batch-controller.ts)

pumpScheduler() 在 activeTabs.size < maxConcurrentTasks 时循环:
  1. round-robin 挑一个 status='running' 且还有 URL 的 task
  2. 从该 task 取 nextIndex URL
  3. 开 tab(active: false,不抢焦点)
  4. 注册 activeTabs[tabId] = {taskId, urlIndex, state}
  5. 60s 超时计时器

tab 完成(onTabDone / 超时 / 异常):
  - 移出 activeTabs
  - 对应 task 的 finishedCount++
  - 重新 pumpScheduler 吸下一个 URL(可能是别的 task 的)

网站抓取(engine-manager.ts)

manageQueue() 在 activeTasks < deepScrapeConcurrency 时循环:
  1. 从 MapTaskData(全局表)查 scrape_status=0 的网址
  2. 过滤:域名黑名单 / 单域名并发上限
  3. 开始 processWebsiteScrape(id, websiteUrl)
  4. activeTasks++
  5. 完成后 activeTasks--,重新触发

→ N 个 worker 同时跑,1 任务 / 100 任务行为相同

关键代码位置

文件 位置 作用
batch-controller.ts 1-55 行注释 架构总述(必读)
batch-controller.ts pumpScheduler 地图 Tab 调度核心
batch-controller.ts pumpPager 翻页 Fetch 串行处理
task-manager.ts pumpTasks 任务级调度(决定哪些 task 是 running)
engine-manager.ts manageQueue 网站抓取调度核心
engine-manager.ts domainActive Map 单域名并发计数(防对方站打挂)

字段速查(与字段语义对照)

字段 全局/单任务 控制什么 上限
maxConcurrentTasks 全局 activeTabs.size 上限 1-10
pagerConcurrency 全局 翻页 Fetch 并发 1-5
deepScrapeConcurrency 全局 网站抓取 worker 数 1-10
deepScrapeDomainConcurrency per-域名 单 hostname 同时几个 worker 1-10
requestConcurrency 已废弃(v0.10.0+)

详见 settings-field-semantics.md

易踩坑

⚠️ 坑 1:望文生义命名 → 误导用户(反复犯)

  • v0.10.12:maxConcurrentTasks label 写成「= 同时进行的任务数」 → 错
  • v0.10.14:deepScrapeConcurrency label 写成「同时挖几个网站」 → 错

正确心智:池子里有 N 个 worker,从全局队列抢任务。与任务数完全无关

⚠️ 坑 2:以为 1 个任务用不满 N 个 Tab

错。1 个任务的 URL 列表(关键词 × 城市 = 笛卡尔积)可能 100+ 个 URL,能用满任何 N。

⚠️ 坑 3:以为 deepScrapeConcurrency 等于「网站个数」

错。它是 URL 抓取并发数。单网站可能有 10 个 URL(multi-keyword 重复商家),如果 deepScrapeDomainConcurrency=2,则该网站最多 2 个 worker,剩 8 个排队。

修改这块时要 / 不要做什么

  • ✅ 加新调度字段 → 必须在本文件加一行 + 字段对照表
  • ✅ 改 round-robin 算法 → 必须在「调度算法」章节同步
  • ❌ 不要在 settings UI 出现「per-task」字眼(除非真是 per-domain)
  • ❌ 不要直接读 requestConcurrency(已废弃)

版本里程碑

版本 事件
v0.9.x per-task 调度器,多任务撞车
v0.10.0 共享队列重写(本架构起点)
v0.10.12 修正 maxConcurrentTasks label 误导
v0.10.14 修正 deepScrapeConcurrency label 误导 + 加跨根域跟随