feat: stabilize multi-region runtime sync and worker orchestration

This commit is contained in:
root
2026-04-27 15:48:12 +08:00
parent 7cbde2aa78
commit 215a364891
137 changed files with 31931 additions and 1943 deletions

84
docs/shenhe.md Normal file
View File

@@ -0,0 +1,84 @@
可以,按“尽量省额度但不丢高风险”的思路,我建议你把全项目审核拆成 3 轮,目标控制在 `800万 ~ 1200万 tokens`
**先排除**
第一轮先不要审这些,不然额度会被白白吃掉:
- `release/`
- `.venv/`
- `node_modules/`
- `domain-web/package-lock.json`
- `domainCheck/app/sdk_leg.js`
- `domainCheck/detect/sdk_leg.js`
- 运行时产物、日志、快照、历史 night runs
- 大 JSON 词库这类静态数据,除非代码直接依赖逻辑可疑
**三轮顺序**
1. 控制面与数据正确性
预算:`300万 ~ 450万`
- `domain-api/app/services/detect_job_service.py`
- `domain-api/app/services/detect_service.py`
- `domain-api/app/services/runtime_status_service.py`
- `domain-api/app/services/dashboard.py`
- `domain-api/app/services/sync_record_service.py`
- `domain-api/app/services/sync_push_service.py`
- `domain-api/app/services/worker_control_service.py`
- `domain-api/app/services/settings_service.py`
- `domain-api/app/services/cluster_runtime_service.py`
- 对应 `routes/` 和关键测试
这一轮最值钱,因为它直接查:
- 页面显示为什么和现场不一致
- sync 为什么会把旧状态盖新状态
- runtime projection / queue health / active job 是否互相打架
- 配置下发和节点实际执行是否一致
2. Worker 并发与执行链
预算:`300万 ~ 400万`
- `domainCheck/detect_worker.py`
- `domainCheck/app/utils/database.py`
- `domainCheck/app/detectors/`
- `domainCheck/detect/`
- `domainCheck/tests/` 里和并发、连接池、超时、代理有关的测试
这一轮重点查:
- 多进程 / 多线程是否真能提升吞吐
- DB 连接池、代理池、任务领取链有没有硬瓶颈
- 内存为什么高、进程为什么空转
- 超时、重试、降级逻辑是否会拖垮吞吐
3. 发布、运维、前端展示
预算:`200万 ~ 300万`
- `domain-api/deploy/`
- `domain-api/app/node_agent.py`
- `domain-api/app/services/ops_*`
- `domain-web/src/views/detect/`
- `domain-web/src/views/runtime/`
- `domain-web/src/views/settings/`
- systemd 模板、迁移/发布脚本
这一轮重点查:
- 发布链和实际运行是否一致
- 多实例 worker 的部署是否完整
- 前端是否误导运维判断
- 迁移、接管、rollout 有没有高风险坑
**模型建议**
- 第 1 轮:`gpt-5.4 + xhigh`
- 第 2 轮:`gpt-5.4 + xhigh`
- 第 3 轮:`gpt-5.4 + high`
这样通常能把额度压在你要的区间里。
**输出方式**
每轮都只要这 3 类结果,最省额度:
- `P0/P1` 真实问题
- 影响面
- 修复建议
不要第一轮就让模型写大篇架构说明,不然额度会烧很快。
一句话版:
先审 `domain-api` 的状态/同步链,再审 `domainCheck` 的并发执行链,最后审 `deploy + ops + 前端展示`;按这个顺序,`800万~1200万 tokens` 是有机会压住的。
如果你要,我下一步可以直接给你生成一份“第一轮审核清单”,精确到文件名单。

View File

@@ -62,7 +62,65 @@ controller syncer 批量同步海外主库
controller finalizer 标记本次流程完成
发现问题
主体功能
1.去后台点击 聚名获取删除域名入库
2.按后台勾选 的检测选项,按顺序逐个去跑
3.正常逻辑是按顺序 一步一步前面的处理完了,再往下一个处理,最大进程跑起来,直到所有任务跑完
现在遇到的问题是:
1.后台展示文案的歧义非常大;运营很难理解
比喻托管节点:这个应该就是 服务器管理,现在你把进程也算是一个节点,全部放到这个列表,我看起来都蒙,如果需要你可以加多一个进程管理不就好了,不要混一起
现在后台应该有 机器/进程/线程,节点到底是啥?现在已经乱了,
运行配置这块也是 节点独立线程覆盖 你把所有进程都列出来配置 线程数量,这个不需要的,所有进程的线程数量全部走默认的,进程那么大不会人工管理的,设计很不合理,
检测管理页面:日志输出窗口 这块也是
参与节点3 这个应该改成 参与 服务器
参与进程62 参与 进程
参与 线程
运行中127
线程127 / 74000参与节点汇总
要让人一眼看明白
2.最重要一点后台页面的数据展示很多都是不符合实际的很多一点为啥黑名单一直是0这块不肯定的这个正常清空最少命中80%
进程和线程一直跑不起来,这个最致命,优化了好几日了,知道目前还是没跑通完整流程
3.概览
步骤队列
看每一步堆积、吞吐和失败快速判断到底卡在注册、百度、360、爱站还是站长之家。 这块应该按后台勾选设置的顺序拍下来,正常任务完成也是,一个跑完才会往下推,才会往下一个跑
4.检测的进程和线程 机器 不稳定,不会自动检测 自动跑起来,就是最大性能没有跑起来
5.如果大陆controller 的DB链接数量是瓶颈那可以每个大陆 机器都开启db+redis 反正每个机器的配置都很高的,只要有效率能提速
6.还有一个但海外机器绝对不参与worker 检测
一句话结论:
线上 hotfix 收口基本完成
下一步最该继续的是代理供给优化,不是回到代码审核
我下一步建议就直接转到代理链路,继续收:
为什么多实例同时刷新时会被代理源限流
是否要继续压低单实例补货批量/频率
是否要做更强的跨进程代理补货协调
大陆处理好的跑完流程域名是否返回海外机器勾选状态
域名检测流程设计

View File

@@ -0,0 +1,229 @@
# 后台运行观察口径
> 说明:这份文档主要解释页面怎么看。当前线上执行口径与判断顺序,统一以 [当前线上最终Runbook.md](/www/wwwroot/getDomain/docs/当前线上最终Runbook.md) 为准。
更新时间:`2026-04-24 14:24`
## 先说结论
以后不要先盯散乱日志。
日志只是辅助定位。
现在优先看后台 `运行中心` 第一屏。
那里已经会直接告诉你:
- 全库总盘子有多少
- 当前活跃批次到底有多少
- 近 15 分钟真实推进了多少
- 当前到底算不算真跑起来了
- 还有多少待处理
- 真正有多少进程和线程在干活
- 最近 15 分钟处理了多少
- 失败多不多
- 黑名单有没有推进
- 当前最忙的是哪些进程
- 主要积压卡在哪几个步骤
只有在后台页面临时不可用,或者你想在终端里持续盯时,才再用下面这条命令:
```bash
cd /www/wwwroot/getDomain
./.venv/bin/python tools/runtime_observer.py
```
如果想持续盯着看,就用:
```bash
cd /www/wwwroot/getDomain
./.venv/bin/python tools/runtime_observer.py --watch 5
```
它会把和后台首屏同一套核心数字收成一屏,不用再自己去拼:
- 当前任务是谁
- 还有多少待处理
- 真正有多少进程和线程在干活
- 最近 15 分钟到底处理了多少
- 失败多不多
- 黑名单有没有推进
- 哪几个节点真的在跑
- 每一步卡在哪
## 先看哪 4 组数
脚本里最重要的是这 4 行:
### 1. 任务积压
看:
- `pending`
- `claimed`
- `running`
这组数回答的是:
- 还有多少活没做
- 有没有任务已经被领走
- 当前有多少任务处于运行中
### 2. 结果产出
看:
- `completed`
- `failed`
- `blacklisted`
这组数回答的是:
- 成功推进了多少
- 失败了多少
- 黑名单命中了多少
### 3. 执行面
看:
- `active_processes`
- `active_threads`
- `max_threads`
这组数回答的是:
- 现在到底有多少执行实例真的在动
- 当前用了多少线程
- 当前理论上限是多少
### 4. 最近吞吐
看:
- `processed_recent`
- `per_minute`
- `failed_recent`
- `blacklisted_recent`
这组数回答的是:
- 最近 15 分钟有没有真实推进
- 大概每分钟能跑多少
- 最近失败是不是很多
- 最近有没有新的黑名单命中
## 怎么判断“真跑起来了”
满足下面这 3 条,才算真跑:
1. `pending` 还有积压
2. `active_threads` 不是 0
3. `processed_recent` 持续增长
如果只是页面上显示 `running`,但:
- `active_threads` 接近 0
- `processed_recent` 也接近 0
那更像是:
- 旧状态残影
- 口径没收准
- 或者任务挂着但没真正消化
## 怎么快速判断卡在哪
### 情况 1`pending` 很高,`active_threads` 很低
说明更像:
- 没真正拉起执行面
- worker 没开始干活
- 或者任务没正确分发进去
### 情况 2`active_threads` 不低,但 `failed_recent` 很高
说明更像:
- 不是没跑
- 而是外部步骤在大量失败
- 常见就是代理、RDAP、爱站、时光机这类链路超时
### 情况 3`completed_recent` 很低,`blacklisted_recent` 也很低
说明更像:
- 线程虽然在跑
- 但结果大多没形成有效推进
- 更像在外部失败里空转
### 情况 4节点列表里只有一台在有动作
说明更像:
- 当前真正承担执行的就一台
- 其他节点可能只是在线
- 或者只是历史残影,不是真正参与执行
## 为什么不要先盯日志
因为日志只能回答:
- 某一步报了什么错
- 某个实例刚才做了什么
但它回答不了:
- 现在到底有多少进程真在跑
- 一共跑成功了多少
- 黑名单推进了多少
- 当前整体吞吐是上升还是下降
所以顺序应该固定成:
1. 先看后台 `运行中心` 第一屏
2. 再看节点列表和步骤分布
3. 只有需要深挖时,再看 `runtime_observer.py` 或具体日志
## 一句话记法
先看:
- 有没有积压
- 有没有执行面
- 最近有没有吞吐
这 3 个一起动,才算真的在跑。
## 现在先看哪 3 块
后台第一屏已经拆成这 3 个核心口径:
### 1. 总盘子(全库)
回答的是:
- 海外主库现在一共有多少域名
- 全库还有多少待检测
- 全库已经通过、失败、黑名单各多少
### 2. 当前活跃批次
回答的是:
- 当前正在跑的是哪一批
- 这一批的展示口径有多少
- 这一批的原始口径有多少
这一块非常重要。
它不是全库总量。
### 3. 近窗吞吐
回答的是:
- 最近 15 分钟到底推进了多少
- 每分钟大概多少
- 最近完成、失败、黑名单各多少

103
docs/审核顺序.md Normal file
View File

@@ -0,0 +1,103 @@
# 第一轮审核清单
第一轮目标:先查控制面、运行态聚合、同步链、节点控制和配置分发。
建议模型与强度:
- `gpt-5.4`
- `xhigh`
建议额度:
- `300万 ~ 450万 tokens`
本轮不看:
- `release/`
- `.venv/`
- `node_modules/`
- 运行时日志、快照、night runs
- 大型静态资源和锁文件
## 审核顺序
### P0运行态与页面口径
1. [domain-api/app/services/detect_job_service.py](/www/wwwroot/getDomain/domain-api/app/services/detect_job_service.py)
重点看:`active_job``runtime_snapshot``runtime_ingest``distributed_node_stats``display_*` 字段是否会互相覆盖。
2. [domain-api/app/services/detect_service.py](/www/wwwroot/getDomain/domain-api/app/services/detect_service.py)
重点看:检测控制页口径、聚合线程数、参与节点/参与进程统计、零值回退逻辑。
3. [domain-api/app/services/runtime_status_service.py](/www/wwwroot/getDomain/domain-api/app/services/runtime_status_service.py)
重点看:运行中心聚合、`queue_health` 与 backlog 对齐、活跃任务判定。
4. [domain-api/app/services/dashboard.py](/www/wwwroot/getDomain/domain-api/app/services/dashboard.py)
重点看:首页总览是否复用旧快照、`queue_display_*``active_job` 是否一致。
5. [domain-api/app/api/routes/detect.py](/www/wwwroot/getDomain/domain-api/app/api/routes/detect.py)
重点看:检测控制接口有没有直接透传脏口径。
6. [domain-api/app/api/routes/runtime.py](/www/wwwroot/getDomain/domain-api/app/api/routes/runtime.py)
重点看:运行中心接口是否二次加工错误。
7. [domain-api/app/api/routes/dashboard.py](/www/wwwroot/getDomain/domain-api/app/api/routes/dashboard.py)
重点看:首页口径是否和服务层一致。
### P0同步链与投影链
8. [domain-api/app/services/sync_record_service.py](/www/wwwroot/getDomain/domain-api/app/services/sync_record_service.py)
重点看:`runtime_projection``runtime_ingest`、未来时间记录、重复投影、自愈逻辑。
9. [domain-api/app/services/sync_push_service.py](/www/wwwroot/getDomain/domain-api/app/services/sync_push_service.py)
重点看:同步触发、推送条件、失败重试、是否会把旧状态推成新状态。
10. [domain-api/app/services/cluster_runtime_service.py](/www/wwwroot/getDomain/domain-api/app/services/cluster_runtime_service.py)
重点看集群节点汇总、心跳、stale/offline 判定。
11. [domain-api/app/services/debug_event_service.py](/www/wwwroot/getDomain/domain-api/app/services/debug_event_service.py)
重点看:调试事件是否参与运行态推断,是否会误导当前任务。
### P0节点控制与配置分发
12. [domain-api/app/services/worker_control_service.py](/www/wwwroot/getDomain/domain-api/app/services/worker_control_service.py)
重点看worker 进程探测、当前状态读取、systemd 口径和实际进程口径是否一致。
13. [domain-api/app/services/settings_service.py](/www/wwwroot/getDomain/domain-api/app/services/settings_service.py)
重点看:`process_count``thread_count`、节点级覆盖、多实例父子节点映射。
14. [domain-api/app/services/runtime_settings_service.py](/www/wwwroot/getDomain/domain-api/app/services/runtime_settings_service.py)
重点看:运行态配置来源、热配置优先级、页面修改后是否真生效。
15. [domain-api/app/services/runtime_control_service.py](/www/wwwroot/getDomain/domain-api/app/services/runtime_control_service.py)
重点看:启动/停止/恢复对多实例 worker 是否安全。
### P1检测任务主链
16. [domain-api/app/services/detect_run_service.py](/www/wwwroot/getDomain/domain-api/app/services/detect_run_service.py)
重点看运行记录、cycle 事件、页面日志来源。
17. [domain-api/app/services/domains_service.py](/www/wwwroot/getDomain/domain-api/app/services/domains_service.py)
重点看:域名主数据是否和检测任务状态有交叉写入风险。
18. [domain-api/app/services/import_task_service.py](/www/wwwroot/getDomain/domain-api/app/services/import_task_service.py)
重点看:导入任务是否影响 backlog、是否能造成页面统计失真。
## 必看测试
### 直接对应运行态/聚合
1. [domain-api/tests/test_detect_job_service.py](/www/wwwroot/getDomain/domain-api/tests/test_detect_job_service.py)
2. [domain-api/tests/test_detect_service_status_fallback.py](/www/wwwroot/getDomain/domain-api/tests/test_detect_service_status_fallback.py)
3. [domain-api/tests/test_runtime_status_service.py](/www/wwwroot/getDomain/domain-api/tests/test_runtime_status_service.py)
4. [domain-api/tests/test_dashboard_service.py](/www/wwwroot/getDomain/domain-api/tests/test_dashboard_service.py)
### 直接对应同步链
5. [domain-api/tests/test_sync_record_service.py](/www/wwwroot/getDomain/domain-api/tests/test_sync_record_service.py)
6. [domain-api/tests/test_sync_push_service.py](/www/wwwroot/getDomain/domain-api/tests/test_sync_push_service.py)
7. [domain-api/tests/test_cluster_runtime_service.py](/www/wwwroot/getDomain/domain-api/tests/test_cluster_runtime_service.py)
### 直接对应节点控制与配置
8. [domain-api/tests/test_worker_control_service.py](/www/wwwroot/getDomain/domain-api/tests/test_worker_control_service.py)
9. [domain-api/tests/test_settings_service.py](/www/wwwroot/getDomain/domain-api/tests/test_settings_service.py)
10. [domain-api/tests/test_detect_api_routes.py](/www/wwwroot/getDomain/domain-api/tests/test_detect_api_routes.py)
11. [domain-api/tests/test_ops_api_routes.py](/www/wwwroot/getDomain/domain-api/tests/test_ops_api_routes.py)
## 本轮输出要求
只输出这 3 类内容,避免烧额度:
- `P0 / P1` 真实问题,带文件和行号
- 影响范围
- 修复建议
不要在第一轮做这些:
- 大篇架构说明
- 文件逐段复述
- UI 细节优化建议
- 发布脚本和迁移功能深挖
## 第一轮完成标准
满足以下条件就可以结束第一轮,转第二轮:
- 能回答“为什么页面显示会和现场不一致”
- 能回答“为什么 sync 会把旧运行态覆盖成新页面口径”
- 能回答“进程/线程配置、页面显示、节点实际执行三者有没有断层”
- 能列出前 `10` 个最值得先修的 `P0/P1` 问题

View File

@@ -0,0 +1,180 @@
# 当前已验证有效的线上参数与改动清单
> 说明:这份文档保留作阶段留档。后续执行和观察,统一以 [当前线上最终Runbook.md](/www/wwwroot/getDomain/docs/当前线上最终Runbook.md) 为准。
最后更新2026-04-25 17:30 左右
适用节点:`mainland-controller-01`
目标:给后续观察和继续优化留一个“当前已经验证有效”的固定基线,不再靠聊天记录回忆。
## 1. 当前线上基底
- 当前 `current` 指向:
`/opt/domaincheck/releases/domaincheck_release_20260424_220158`
- 当前核心服务状态:
- `domaincheck-worker.service = active`
- `domaincheck-sync-agent.service = active`
- `domaincheck-api.service = active`
说明:
- 这台机器现在不是只跑旧 release 原样代码,而是“`220158` 基底 + 多轮热补”。
- 后续如果要正式固化,应该把当前热补内容重新打一版正式 release。
## 2. 当前已确认生效的远端环境参数
以下参数已经在运行中的 `detect_worker.py` 进程环境里确认过,不是只改了文件没重启:
### 单机性能/爱站相关
- `DOMAINCHECK_REGISTER_SINGLE_MACHINE_MODE=1`
- `DOMAINCHECK_SINGLE_MACHINE_AIZHAN_DIRECT_FIRST=1`
- `DOMAINCHECK_AIZHAN_REMOTE_DISCONNECT_DEGRADE=1`
- `DOMAINCHECK_AIZHAN_EXTERNAL_FAST_DEGRADE=1`
- `DOMAINCHECK_PROXY_STEP_MAX_ATTEMPTS_AIZHAN=2`
- `DOMAINCHECK_PROXY_STEP_MAX_SECONDS_AIZHAN=10`
- `DOMAINCHECK_AIZHAN_TIMEOUT_PROXY=1.8`
- `DOMAINCHECK_AIZHAN_TIMEOUT_DIRECT=2.4`
### Wayback 相关
- `DOMAINCHECK_PROXY_STEP_MAX_ATTEMPTS_WAYBACK=2`
- `DOMAINCHECK_PROXY_STEP_MAX_SECONDS_WAYBACK=8`
- `WAYBACK_CDX_TIMEOUT=3`
- `WAYBACK_SNAPSHOT_TIMEOUT=2`
- `WAYBACK_RETRY_COUNT=1`
- `WAYBACK_DOMAIN_CONCURRENCY=1`
- `WAYBACK_MAX_RECORDS=2`
- `WAYBACK_TRANSIENT_BACKOFF_SECONDS=0.5`
## 3. 当前已确认上线的代码改动方向
这些不是“计划中”,而是已经热补到 `mainland-controller-01` 并跑起来的:
### 调度/分发侧
- 预补货提前触发,不再等批次快空了才补。
- 预补货时不让拿到锁的那个 worker 先独吞下一批任务。
- `pipeline` 推进和主动 `pull_tasks` 做了共享锁,减少 60 个 worker 同时拆批次。
- `session_replaced -> queued_before_start` 这条重复唤起链已经加护栏压住。
- `claim / finalize / release` 这些本地 DB 重链做过多轮减重,分钟级长尾已经明显掉下来。
### 注册/站点检测侧
- 注册检测保留单机性能模式。
- 百度、360、爱站都已经不再停留在过于激进的超时口径。
- `爱站` 已经补到:
- 单机模式下首轮可直连优先
- `RemoteDisconnected` 可直接降级
- 现在又进一步支持“外部依赖异常快速降级”,不再把整轮预算白白耗完
### Wayback 侧
- `wayback` 已经从“自己 backoff 把自己打死”收住。
- 当前远端新版逻辑里:
- `latest_cdx` transient 时,不再硬打额外的 `records_cdx`
- backoff 已收短到 `0.5s`
- 当前剩下主要是 `web.archive.org` 本身慢/拒绝连接
## 4. 这轮真正验证出来的效果
下面这些是已经实测到的,不是预估:
### 4.1 机器确实吃起来了
较早一轮稳定快照里,`mainland-controller-01` 已经出现:
- `60` 个节点全在线
- `53` 个子 worker 的 `active_threads > 0`
- `active_thread_count = 13531`
后续观察里,最近 2 分钟也长期能看到:
- `59` 个 fresh worker
- 大约 `51 ~ 56` 个 worker 有真实线程数大于 `0`
- 真实线程总和大约在 `3.8 万 ~ 4.2 万`
这说明当前不是“进程活着但没吃活”,而是确实在跑。
### 4.2 内存高位问题先被压下来了
滚动重启并替换旧 RSS 后,现场有过一轮明显回落:
- `Mem used ≈ 31Gi`
- `available ≈ 93Gi`
- `60` 个 worker 总 RSS 约 `23.9Gi`
说明“旧 RSS 不退”这层已经不是最早那种危险状态。
### 4.3 爱站与 Wayback 的尾部外部链,已经从“卡住不产出”变成“持续产出”
观察窗:`2026-04-25 17:17:18``17:29:23`
- `completed_total: 50786 -> 51298`,净增 `512`
- `aizhan_completed: 7718 -> 7936`,净增 `218`
- `wayback_completed: 3068 -> 3362`,净增 `294`
- 这段窗口里:
- `aizhan_failed = 0`
- `wayback_failed = 0`
这个结论很重要:
- 现在已经不是“压住 failed但 completed 不动”
- 而是“failed 压住了,同时 completed 也在持续增长”
### 4.4 爱站快速降级这刀确实有作用
更早一段观察里,`爱站 failed` 还是会堆。
在把 `DOMAINCHECK_AIZHAN_EXTERNAL_FAST_DEGRADE=1` 灰到远端后,后面的观察窗里:
- `aizhan_failed` 被压到 `0`
- `aizhan_completed` 持续增长
这说明当前主链里,对爱站这类外部依赖异常,“快速降级继续”是有效的。
## 5. 当前怎么理解这些数字
当前最应该盯的不是:
- 单次 `claimed`
- 单次 `pending`
- 某一个瞬间的 `running`
因为这是 `domain_pipeline`,前一步跑完会继续生成下一步,数字天然会波动。
当前更应该盯的是:
1. `completed_total` 是否持续增长
2. `aizhan_completed / wayback_completed` 是否持续增长
3. `aizhan_failed / wayback_failed` 是否继续维持低位
如果这三组数继续保持当前趋势,就说明这套配置是对的。
## 6. 当前不要再乱动的东西
在下一轮更长观察结束前,建议先不要再继续频繁改:
- worker 进程数
- 每进程线程上限
- claim / backlog 批量参数
- wayback / 爱站超时参数
原因很简单:
现在已经进入“参数开始生效、completed 在涨”的阶段,再频繁动,会把已验证有效的窗口打碎。
## 7. 下一步建议
当前建议先停手观察,不再继续大改参数。
优先做两件事:
1. 继续观察更长窗口
建议至少再看 `30 ~ 60` 分钟,确认:
- `completed_total` 继续增长
- `aizhan_completed / wayback_completed` 继续增长
- `aizhan_failed / wayback_failed` 不重新抬头
2. 之后再做正式固化
把当前这些热补过的代码和远端有效参数,整理进正式 release避免后续机器重启或重新发版时丢失。
## 8. 当前一句话结论
**这轮已经验证到:当前 `mainland-controller-01` 上这套“单机性能模式 + 外部依赖快速降级继续”的组合是有效的,主链已经从“线程忙但不出结果”切到“线程忙,同时持续产出 completed”。**

View File

@@ -0,0 +1,273 @@
# 当前线上最终 Runbook
最后更新2026-04-26 18:12 左右
适用节点:`mainland-controller-01`
这份文档的目标很简单:
后面不再靠聊天记录回忆,也不在多份文档里来回翻。
只看这一份,就知道:
1. 当前线上到底跑在什么基线上
2. 现在先看什么数
3. 什么情况继续观察
4. 什么情况才值得继续动刀
## 1. 当前结论
当前这套线上方案,已经从:
- 线程忙但不出结果
推进到了:
- 线程忙,同时持续产出 `completed`
所以现在的正确策略不是继续乱改参数,而是:
**先稳住,先观察,确认这套已经验证有效的配置能不能持续出结果。**
## 2. 当前线上基线
### 当前基底版本
- `current` 指向:
`/opt/domaincheck/releases/domaincheck_release_20260424_220158`
说明:
- 当前不是纯旧 release 原样运行
- 真实线上状态是:
`220158 基底 + 多轮已热补代码 + 已生效 env`
### 当前服务状态
- `domaincheck-worker.service = active`
- `domaincheck-sync-agent.service = active`
- `domaincheck-api.service = active`
## 3. 当前已确认生效的关键参数
这些参数是**已经在运行进程环境里确认过**的,不是只改了文件。
### 单机性能与爱站相关
- `DOMAINCHECK_REGISTER_SINGLE_MACHINE_MODE=1`
- `DOMAINCHECK_SINGLE_MACHINE_AIZHAN_DIRECT_FIRST=1`
- `DOMAINCHECK_AIZHAN_REMOTE_DISCONNECT_DEGRADE=1`
- `DOMAINCHECK_AIZHAN_EXTERNAL_FAST_DEGRADE=1`
- `DOMAINCHECK_PROXY_STEP_MAX_ATTEMPTS_AIZHAN=2`
- `DOMAINCHECK_PROXY_STEP_MAX_SECONDS_AIZHAN=10`
- `DOMAINCHECK_AIZHAN_TIMEOUT_PROXY=1.8`
- `DOMAINCHECK_AIZHAN_TIMEOUT_DIRECT=2.4`
### Wayback 相关
- `DOMAINCHECK_PROXY_STEP_MAX_ATTEMPTS_WAYBACK=2`
- `DOMAINCHECK_PROXY_STEP_MAX_SECONDS_WAYBACK=8`
- `WAYBACK_CDX_TIMEOUT=3`
- `WAYBACK_SNAPSHOT_TIMEOUT=2`
- `WAYBACK_RETRY_COUNT=1`
- `WAYBACK_DOMAIN_CONCURRENCY=1`
- `WAYBACK_MAX_RECORDS=2`
- `WAYBACK_TRANSIENT_BACKOFF_SECONDS=0.5`
## 4. 当前已确认上线的代码方向
### 调度 / 分发侧
- 预补货提前触发
- 预补货时不再让补货 worker 先独吞下一批
- `pipeline` 推进和主动 `pull_tasks` 做了共享锁
- `session_replaced -> queued_before_start` 这条重复唤起链已压住
- `claim / finalize / release` 这些本地 DB 重链已经做过减重
### 外部步骤侧
- 注册检测保留单机性能模式
- 百度、360、爱站都不再是早期那套过于激进的超时口径
- `爱站` 当前已经具备:
- 首轮可直连优先
- `RemoteDisconnected` 可直接降级
- 外部依赖异常可快速降级继续
- `wayback` 当前已经具备:
- `latest_cdx` transient 时不再硬打额外 `records_cdx`
- backoff 已收短
- 主要剩下 `web.archive.org` 本身慢/拒绝连接
## 5. 当前已经验证出来的效果
### 5.1 机器已经真正吃起来
已观察到:
- `60` 个节点全在线
- 多次快照里 `51 ~ 56` 个 worker 有真实线程
- 真实线程和长期在 `3.8 万 ~ 4.2 万`
所以当前不是“进程活着但没干活”,而是确实在跑。
### 5.2 内存高位问题先被压下来
滚动重启释放旧 RSS 后,现场出现过一轮明显回落:
- `Mem used ≈ 31Gi`
- `available ≈ 93Gi`
- `60` 个 worker 总 RSS ≈ `23.9Gi`
说明“旧 RSS 不退”这层已经先被压住。
### 5.3 当前真正最重要的验证结果
观察窗:`2026-04-25 17:17:18``17:29:23`
- `completed_total: 50786 -> 51298`,净增 `512`
- `aizhan_completed: 7718 -> 7936`,净增 `218`
- `wayback_completed: 3068 -> 3362`,净增 `294`
- 这段窗口里:
- `aizhan_failed = 0`
- `wayback_failed = 0`
这说明:
- 现在已经不是“压住 failed但 completed 不动”
- 而是“failed 被压住,同时 completed 持续增长”
## 6. 当前最该看的数
后面观察时,优先级固定按这个顺序:
1. `completed_total`
2. `aizhan_completed`
3. `wayback_completed`
4. `aizhan_failed`
5. `wayback_failed`
6. `running / claimed / pending`
顺序不要反过来。
原因:
- `claimed / pending``domain_pipeline` 下天然会波动
- `completed` 才是最真实的产出
## 7. 当前正确的判断方法
### 7.1 什么叫“继续观察就行”
只要看到下面这种趋势,就先不要再改:
- `completed_total` 持续增长
- `aizhan_completed` 持续增长
- `wayback_completed` 持续增长
- `aizhan_failed / wayback_failed` 维持低位或归零
这说明:
- 当前主链是通的
- 当前配置方向是对的
- 现在最应该做的是稳住,不是继续乱调
### 7.2 什么叫“又卡住了”
只有出现下面这些情况,才值得继续下刀:
#### 情况一
`completed_total``30 ~ 60` 分钟窗口里基本不动
说明:
- 虽然线程在跑
- 但结果没有真正落地
#### 情况二
`aizhan_failed``wayback_failed` 又明显抬头
说明:
- 当前“快速降级继续”的策略还不够稳
#### 情况三
`running` 长时间很高,但 `aizhan_completed / wayback_completed` 不再增长
说明:
- 新的主瓶颈又出现了
## 8. 当前建议观察窗口
建议至少继续看 `30 ~ 60` 分钟。
正确做法:
1. 记录一组起始值
2. 每隔 `10 ~ 15` 分钟看一次
3. 至少看 `3 ~ 4` 个点
不要只看一两个瞬间。
## 9. 当前先不要再碰的东西
在这轮观察结束前,先不要继续改:
- worker 进程数
- 每进程线程数
- claim 批量
- backlog 批量
- 爱站超时
- wayback 超时
- 其他步骤的重试窗口
原因很简单:
- 现在已经进入“开始稳定产出”的阶段
- 再频繁改参数,只会把已经验证有效的窗口打碎
## 10. 当前一句话执行原则
**先稳住,先看 `completed` 能不能持续增长;只要结果还在持续出来,就先不要再乱改。**
## 11. `job 876` 尾项释放的当前判断
观察时间:`2026-04-26 18:12` 左右
当前不是“又卡死了”,而是:
- `idx_detect_job_items_release_node_job` 还没转成 `valid=1`
- 但它已经稳定进入 `index validation: scanning table`
- 最新验证进度已经到 `229991 / 6197384`
`codex-job876-tail-watch.service` 这条后台链当前是活的,而且在连续记进度:
- `18:09:51 -> 126725`
- `18:10:52 -> 169564`
- `18:11:52 -> 210067`
- `18:12:22 -> 227856`
同时现场另外两组状态也正常:
- `job 876 = running`
- worker 已恢复到:
- `base=active`
- `templated=199`
- `node_agent=active`
这次现场更准确的判断应该是:
- 已经从“卡住”切成“后台验证扫描中”
- 当前最合理的动作不是再人工乱动
- 应该继续让索引自己扫完
一旦 `idx_detect_job_items_release_node_job` 转成 `valid=1``codex-job876-tail-watch.service` 就会自动继续释放 `876` 的尾项。
## 12. 如果后面还要继续优化,下一刀顺序
只有在更长观察窗口里确认又卡住时,才按这个顺序继续:
1. 先看 `completed_total` 为什么不涨
2. 再看 `aizhan_completed / wayback_completed` 哪个先停
3. 再决定是继续打外部链,还是回头查回写 / 聚合语义
不要一上来就回去改线程、claim、并发。

View File

@@ -0,0 +1,149 @@
# 当前线上运维执行清单
> 说明:这份文档保留作阶段执行稿。后续执行和观察,统一以 [当前线上最终Runbook.md](/www/wwwroot/getDomain/docs/当前线上最终Runbook.md) 为准。
最后更新2026-04-25 17:30 左右
适用节点:`mainland-controller-01`
## 1. 当前目标
当前先不要继续乱调参数。
先确认这一套已经生效的配置,能不能稳定把结果持续跑出来。
现在最重要的不是:
- 再加线程
- 再改 claim
- 再改批量
- 再改超时
现在最重要的是:
1. `completed_total` 能不能持续增长
2. `aizhan_completed / wayback_completed` 能不能继续增长
3. `aizhan_failed / wayback_failed` 会不会重新抬头
## 2. 当前已经验证有效的方向
这一轮已经确认有效的不是单点,而是整套思路:
- 单机性能模式保留
- 任务已经能铺到更多 worker 上
- `爱站` 支持更快降级继续
- `wayback` 已经减少了额外的 `records_cdx` 压力
- 当前主链已经从“线程忙但不出结果”变成“线程忙,同时持续产出 completed”
## 3. 当前观察时先看什么
优先看这 6 组数:
1. `completed_total`
2. `aizhan_completed`
3. `wayback_completed`
4. `aizhan_failed`
5. `wayback_failed`
6. `running / claimed / pending`
判断顺序不要乱:
1. 先看 `completed_total` 有没有涨
2. 再看 `aizhan_completed / wayback_completed` 有没有涨
3. 最后才看 `failed``pending`
原因:
- `claimed / pending``domain_pipeline` 下会波动,不能单独看
- `completed` 才是最真实的产出
## 4. 当前正确的判断口径
### 说明一
如果看到:
- `running` 很高
- `claimed / pending` 在波动
-`completed_total` 也在持续涨
这不是问题。
这说明当前机器正在持续消化任务。
### 说明二
如果看到:
- `aizhan_failed = 0`
- `wayback_failed = 0`
- `aizhan_completed / wayback_completed` 继续涨
这说明现在这套“外部依赖快速降级继续”是对的。
### 说明三
如果看到:
- `running` 很高
-`completed_total` 长时间完全不涨
这才说明主链又卡住了,需要继续排。
## 5. 当前建议观察窗口
建议至少看 `30 ~ 60` 分钟,不要只看一两个瞬间。
推荐做法:
1. 先记一组起始值
2. 每隔 `10 ~ 15` 分钟看一次
3. 连看至少 `3 ~ 4` 个点
只看单点,很容易误判。
## 6. 什么情况说明继续观察就行
只要出现下面这种趋势,就先不要动参数:
- `completed_total` 持续增长
- `aizhan_completed` 持续增长
- `wayback_completed` 持续增长
- `aizhan_failed / wayback_failed` 维持低位或归零
这说明当前主链是通的,应该先稳住。
## 7. 什么情况才需要继续下刀
只有出现下面这些情况,才值得继续改:
### 情况一
`completed_total``30 ~ 60` 分钟窗口里基本不动
说明虽然线程在跑,但结果没有真正落地。
### 情况二
`aizhan_failed``wayback_failed` 又明显抬头
说明当前“快速降级继续”还不够稳。
### 情况三
`running` 长时间很高,但 `aizhan_completed / wayback_completed` 不再增长
说明新的主瓶颈又出现了。
## 8. 当前不要再碰的东西
在这轮观察结束前,先不要继续改:
- worker 进程数
- 每进程线程数
- claim 批量
- backlog 批量
- wayback 参数
- 爱站参数
原因很简单:
现在已经进到“开始稳定产出”的阶段,频繁动参数,只会把已验证有效的窗口打碎。
## 9. 当前一句话执行原则
**先稳住,先观察 completed 是否持续增长;只要结果还在持续出来,就先不要再乱改。**

View File

@@ -0,0 +1,162 @@
# 新机器接手交接
## 1. 目标
这次不是继续在当前机器上排障,而是:
- 把当前代码整理后提交到 Git
- 在新服务器上重新部署验证
- 让新的 Codex 接手继续推进
当前最重要的未完成主线只有一条:
- `domain-api/app/services/sync_push_service.py``_load_pushable_projections()` 的修复,需要在远端稳定部署后验证:
- `detect_result_projection` push 是否恢复
- `detect_result_ingest` 是否出现
## 2. 当前代码结论
已经确认并完成的代码侧修复:
- `domain-api/app/services/sync_push_service.py`
- 修复 `_load_pushable_projections()` 选择逻辑
- 旧问题:最新 `detect_result_projection` 明明存在且没有 push attempt但候选集为空
- 现修复:候选改为优先按最新 projection 选择
- `domain-api/tests/test_sync_push_service.py`
- 已补回归测试
本地验证已完成:
- `py_compile` 通过
- `python -m unittest tests.test_sync_push_service` 通过
## 3. 当前卡点
不是代码不清楚,而是当前远端 SSH 不稳定:
- `121.204.244.188:22` TCP 可连接
- 但 SSH banner 经常超时
- Paramiko 常见报错:
- `SSHException('No existing session')`
- `Error reading SSH protocol banner`
因此当前没有拿到“远端已发布并生效”的硬结果。
## 4. 新机器接手后的首要动作
新机器接手后,先不要扩散排查面,只做这一个最小动作:
1. 发布 `domain-api/app/services/sync_push_service.py`
2. 重启 `domaincheck-sync-agent.service`
3. 只验证两项:
- `detect_result_projection` push 是否恢复
- `detect_result_ingest` 是否出现
## 5. 关键背景
项目根目录:
- `/www/wwwroot/getDomain`
主要子目录:
- `domain-api/`
- `domain-web/`
- `domainCheck/`
- `docs/`
当前远端主机:
- `mainland-controller-01`
- `121.204.244.188`
已确认的远端运行时 region 配置:
- `NODE_REGION=mainland`
- `SYNC_SOURCE_REGION=mainland`
- `SYNC_TARGET_REGION=overseas`
已确认的数据事实:
- mainland 本地已经生成了 `detect_result_projection`
- surrogate job 例如:
- `job_id=909`
- `job_code=sync-overseas-28618`
- worker 事件已写入 mainland 本地库
- 真正未打通的是 `detect_result_projection` 的 push 选择阶段
## 6. 建议提交范围
建议优先提交真正的代码改动,不要把运行产物一起带上。
建议提交:
- `domain-api/`
- `domain-web/`
- `domainCheck/`
- `tools/`
- 需要保留的文档 `.md`
建议重点确认本次必须包含:
- `domain-api/app/services/sync_push_service.py`
- `domain-api/tests/test_sync_push_service.py`
## 7. 明确不要提交的内容
以下属于运行产物、临时文件或本地探针,不建议提交:
- `docs/ops_center_runtime/night_runs/step_mix_*/`
- `docs/ops_center_runtime/chinaz_gray_runs/`
- `docs/_tmp_regression/`
- `.codex-release-probe.txt`
- `*.bak_*`
- `*.log`
- `*.pid`
当前工作区里特别要排除的项目:
- `domainCheck/app/utils/database.py.bak_20260426_220818_active_job_cache_tune`
- `domainCheck/detect_worker.py.bak_20260426_220818_active_job_cache_tune`
- `docs/ops_center_runtime/night_runs/step_mix_20260426_051718_smoke_p200/`
- `docs/ops_center_runtime/night_runs/step_mix_20260426_051754_smoke_p200/`
- `docs/ops_center_runtime/night_runs/step_mix_20260426_052641_debug_stepmix/`
- `docs/ops_center_runtime/night_runs/step_mix_20260426_053252_mainland_step_mix_p200_t800/`
- `docs/ops_center_runtime/chinaz_gray_runs/`
- `.codex-release-probe.txt`
## 8. 已跟踪但不建议把这次删除提交进去的文件
这些文件目前显示为已删除,但更像本地运行态/配置文件,不建议在这次“迁移到新服务器”的提交里带上删除:
- `domainCheck/app/thread_count.json`
- `domainCheck/node_thread_counts.json`
- `domainCheck/runtime/runtime_settings.json`
- `domainCheck/runtime/sensitive_words.json`
- `domainCheck/runtime_settings.json`
- `domainCheck/thread_count.json`
处理建议:
- 如果这些删除不是你明确想做的配置收口,请在提交前恢复
- 不要把“本地运行时删掉了某个 JSON”误当作产品代码改动提交
## 9. 提交前建议
提交前建议至少做这几步:
1. `git status --short`
2. 把运行产物和备份文件排除掉
3. 只保留代码、测试、必要文档
4. 单独复查:
- `sync_push_service.py`
- `test_sync_push_service.py`
- `.gitignore`
- 本交接文档
## 10. 给新机器上的 Codex 的一句话
接手后不要重新发散排查 worker、projection 生成、事件落库链;这些已经基本收敛。优先完成 `sync_push_service.py` 的远端生效验证,只盯:
- `detect_result_projection` push
- `detect_result_ingest`

417
docs/测试优化分析.md Normal file
View File

@@ -0,0 +1,417 @@
# 测试优化分析
> 说明:这份文档保留作历史分析记录。当前线上执行基线与观察方法,统一以 [当前线上最终Runbook.md](/www/wwwroot/getDomain/docs/当前线上最终Runbook.md) 为准。
更新时间:`2026-04-24 14:00`
## 这份文档是干什么的
这份文档不是讲理想情况,而是把这次已经做过的单机压测结果,和当前现场“根本没真正跑起来”的现状,放在一起说清楚。
目的很直接:
- 先把已经验证过的参数结论留档
- 再把现在为什么看起来线程上限很高、实际只跑了很少线程说清楚
- 给后面继续优化的人一个明确顺序,别再一边主链没跑通,一边继续盲目加并发
## 一句话结论
早上那轮“只测一台大陆机器”的压测,已经得出过一个单机最优值:
- `mainland-controller-01` 单机跑时,当前最优参数是 `35 进程 / 900 线程`
但这不等于项目现在已经能按这个参数稳定跑。
`2026-04-24 13:22` 左右控制面看到的实时状态,这套链路目前更像是:
- 任务还挂着
- 页面也还能显示“运行中”
- 但真正参与执行的只有很少几个桶
- 总体上属于“没有真正跑起来”
所以现在的主矛盾不是“继续把线程调大”,而是“先让主流程真的跑起来”。
## 这次单机压测是怎么测的
这轮压测不是全链路压测,而是一个故意收缩后的测试环境。
当时做了这几件事:
- 先停掉 `121.204.244.248`,也就是 `mainland-worker-01`
- 只保留 `mainland-controller-01` 单机承担检测
- 停掉 controller 上的 `domaincheck-sync-agent.service`
- 不再继续额外增加新的 Redis 限制动作
- 直接在 controller 本地数据库里看 60 秒窗口真实完成量和更新量
这么做的目的,不是模拟线上真实规模,而是先排除多机互相干扰,看单机能跑到什么水平。
## 单机压测结果
压测时间段大致是 `2026-04-24 05:14 - 05:28`
当时 controller 本地 60 秒窗口结果如下:
| 参数 | 60 秒完成量 | 60 秒更新量 | Redis 连接数 | 内存已用 | 可用内存 | 实际活跃 worker |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| `35/900` | `4092` | `22267` | `751` | `73.6G` | `54.3G` | `35` |
| `40/1000` | `1885` | `1544` | `793` | `75.1G` | `52.9G` | `40` |
| `45/1100` | `388` | `11347` | `818` | `75.7G` | `52.3G` | `45` |
| `50/1200` | `0` | `12118` | `823` | `75.9G` | `52.1G` | `45` |
## 压测结论
从这轮结果里,可以直接得出 4 个结论:
### 1. 单机最优值不是越大越好
当前这套链路下,`35/900` 明显优于后面的更大参数。
也就是说,继续加进程、加线程,并没有带来更高完成量,反而更差。
### 2. 当前单机稳定上限大约在 `45/1100`
`45/1100` 还能勉强活住。
但它已经不是“跑得更快”,只是“还能挂着不死”。
### 3. `50/1200` 已经没有意义
虽然当时把参数抬到了 `50/1200`,但最终实际只活成了 `45` 个 worker而且完成量已经掉到 `0`
这说明不是配置没写进去,而是链路已经先撞到别的瓶颈了。
### 4. 当时真正的瓶颈已经不是 Redis
从这轮压测看Redis 连接数有上升,但没有先爆。
更像是外部检测链路本身先撑不住了,尤其是:
- 注册检测
- 代理池质量
- 会话切换抖动
## 为什么当时测出来 `35/900`,现在页面却只剩 `52` 线程
这就是最容易误判的地方。
早上压测结论成立的前提是:
- 只测 controller 单机
- `mainland-worker-01` 被停掉
- `sync-agent` 被停掉
- 现场是专门为压测收缩过的
而现在你看到的实时状态,已经不是那个压测场景了。
`2026-04-24 13:22` 左右接口返回的状态,现在的现场是:
- `detect/job/active` 里的活动任务还是 `job_id=1`
- 这条任务从 `2026-04-21 23:30:42` 就已经是 `running`
- `raw_items_pending=9478`
- `raw_items_running=1`
- `raw_items_completed=4996`
- `raw_items_failed=486`
同时,`runtime/status` 给出的聚合状态是:
- `参与服务器4`
- `参与进程4`
- `参与线程52`
- `运行中52`
- `线程上限52 / 2701`
这几组数字合起来说明的不是“系统已经高效跑起来”,而是:
- 页面还能看到一些参与桶
- 但真正持续工作的执行面很薄
- 大量任务还躺在队列里,没有被健康地持续消化
## 当前为什么可以直接判断“根本跑不起来”
不是因为页面数字小,而是因为几条关键事实同时出现了:
### 1. 活动任务太老
当前活动任务还是 `2026-04-21` 开出来的老任务。
如果主链真的健康,它不应该拖这么久还挂在 `running`
### 2. 待处理量还很大
当前还剩:
- `9478` 条原始 pending
- `3779` 条 claimed
- `1028` 条 running backlog
但真正展示出来的活跃线程只有 `52`
这个比例说明队列不是空了,而是消化不动。
### 3. 当前节点自己根本不跑本机 worker
当前 `overseas-control-01``runtime/status` 里明确写着:
- `worker_online=false`
- `worker_process_count=0`
- `worker_runtime_message=inactive/dead`
也就是说,现在这个页面看到的“运行中”,本来就不是本机在跑,而是靠远端汇总。
### 4. 远端参与桶很少,而且口径不稳
现在控制面聚合出的参与节点只有:
- `mainland-worker-01`
- `mainland-controller-01-k`
- `mainland-controller-01-a`
- `mainland-controller-01-s`
这和早上单机压测时的 `35` 个 worker根本不是一个级别。
所以你现在看到“线程上限 2701”不要被这个数字迷惑。
它只是汇总上限,不代表这些线程真的都在跑。
## 现在最像什么问题
如果用人话说,现在最像下面这种情况:
- 任务队列里还有很多活
- 但是执行面只剩很薄一层还在动
- 页面还能看到“运行中”,所以看起来像没完全死
- 可真正吞吐已经低到接近“没跑起来”
所以这不是单纯的参数问题,而是主流程状态已经歪了。
## 当前最该先做的事
下一步优化顺序建议固定成这样:
### 第 1 步:先恢复“能持续跑”
先不要继续加进程、加线程。
先确认 4 件事:
- 当前到底是哪几个节点真的在跑
- `mainland-worker-01` 是不是还应该继续停着
- controller 上当前实际活着多少 worker
- 老任务里这些 `claimed/running` 有没有大量卡死项
### 第 2 步:把老任务和会话问题理顺
现在这条 `2026-04-21` 的老任务已经拖太久了。
要优先确认:
- 是不是有很多旧 `session_replaced`
- 是不是有大量任务项一直卡在 `claimed/running`
- 是不是页面显示还在跑,但实际 worker 已经不工作了
### 第 3 步:确认代理链是不是主瓶颈
早上单机压测已经说明Redis 不是先撞墙的那个点。
下一步更值得盯的是:
- `detect_register` 的真实完成率
- 代理池是否经常空
- RDAP 访问是否大量超时
### 第 4 步:只有主链恢复后,才重新做性能优化
## 13:48 补充结论
这轮又确认了一件非常关键的事:
- `mainland-worker-01` 不是“已经彻底消失”,而是“被标记为停用,但之前还在继续上报运行态”
后来实际做了两步处理:
- 先通过远端 `ops job``mainland-worker-01` 本机执行 `systemctl stop domaincheck-worker`
- 再把控制面的状态汇总改成:只要节点在 `ops_managed_nodes` 里是 `is_enabled=false`,就不再算进在线 Worker、参与节点和活跃线程
处理完成后控制面实时口径已经从“2 台参与节点 / 2 个检测进程 / 2 线程”收到了:
- `参与节点1`
- `参与进程1`
- `运行中1`
- 只剩 `mainland-controller-01`
这说明前面那种“明明已经准备单机测了,页面还一直把停用节点算进去”的问题,确实是状态口径 bug不是现场真的还有两台健康执行机一起在跑。
所以从现在开始再看单机测试结果时,要以这条新口径为准:
- `mainland-worker-01` 已停用,不再参与单机压测统计
- 当前有效执行面只剩 `mainland-controller-01`
等主流程恢复成“真的能持续吃队列”之后,再拿 `35/900` 作为第一版基线。
到那时可以按下面顺序再调:
1. 先验证 `35/900` 在恢复后的现场还能不能稳定
2. 如果稳定,再测 `40/1000`
3. 只有吞吐确实提高了,才继续往上加
## 这份文档的最终结论
可以把这次结果浓缩成两句话:
第一句:
- 单机压测已经证明,当前代码和链路下,`mainland-controller-01` 的最佳参数是 `35 进程 / 900 线程`
第二句:
- 但当前线上真正的问题不是“参数不够大”,而是“主流程根本没有持续跑起来”,所以现在继续加线程没有意义
后面再优化时,应该先解决“为什么只剩 52 线程在动”,再谈如何把吞吐重新抬高。
## 2026-04-24 13:35 补充结论
这份文档写完以后,又继续往下排了一轮,现场有两个非常关键的新结论。
### 1. `52` 线程里有明显口径错配
后来继续核对后发现,页面里那组:
- `参与服务器4`
- `参与进程4`
- `参与线程52`
并不是来自 `detect_job_items` 真表本身。
真实数据库里,`job_id=1` 其实只剩:
- `1``running`
而且这条记录最后更新时间还停在 `2026-04-22 15:00:56`,本质上已经是老僵死项。
真正把页面抬到 `52` 的,是一条口径 bug
- 当前 active job 还是老任务 `detect-20260421232649-acfa3d`
- 但控制面又拿到了别的 sync job 的 runtime overlay
- 两套数据被硬合到了一起
简单说就是:
- 任务表是老 job
- 线程数却借用了别的 job 的运行态
### 2. 这条口径 bug 已经修掉了
现在代码已经补成:
- 只有 runtime overlay 的 `job_code / job_id` 和当前 active job 对得上时,才允许覆盖 `queue_health`
- 对不上时,宁可退回真实任务表,也不再把别的 job 的运行态硬套到当前页面上
修完并重启海外控制面 API 之后,实时口径已经从之前的 `52` 收回到了:
- `参与节点2`
- `参与进程2`
- `参与线程2`
当前这 `2` 个真实信号分别来自:
- `mainland-controller-01` 那条老 `running`
- `mainland-worker-01` 当前 heartbeat 上报的 `1` 条活跃线程
这说明一件事:
- 之前那组 `52` 的确主要是错口径,不是实际吞吐
## 目前还剩的真实问题
虽然 `52 -> 2` 这一步已经收准了,但项目还是没有真正恢复健康。
当前还剩 2 个真实问题:
### 1. `mainland-worker-01` 明明已停用,却还在持续推运行态
当前控制面日志里还能持续看到:
- `121.204.244.248` 往海外控制面打 `runtime/debug-ingest`
- 同时也还在打 `ops/agent/pull`
这说明这台机器不是“旧残影”,而是还真的在线、还真的在报活。
所以“已停用”这件事,目前只是在托管配置层停了,但没有真正把它从 Agent / runtime 上报链里摘干净。
### 2. `job_id=1` 这条老任务本身也还没收口
当前老任务依然挂着:
- `job_id=1`
- `job_code=detect-20260421232649-acfa3d`
- `started_at=2026-04-21 23:30:42`
而真实任务表里,它现在已经不是“很多线程在跑”,而是:
- 大部分还在 `pending`
- 只剩 1 条老 `running`
这说明这条老任务本身也需要后续专门处理,不能继续长期挂在 `running`
## 现在更准确的下一步
到这里为止,下一步就更明确了:
1. 先把 `mainland-worker-01` 真的摘掉。
2. 再处理 `job_id=1` 这条老任务的遗留 `running` 项。
3. 等这两件事收完,再重新看当前真实执行面到底还有没有持续吞吐。
也就是说,现在已经不是“继续猜线程参数”的阶段,而是“先把错误执行面和老任务残留清掉”的阶段。
## 新增一版“单机性能模式”给注册检测
这次为了后面继续压单机吞吐,我已经在代码里补了一版只影响“注册状态检测”的性能模式。
它的目标不是改全局架构,而是先把当前最像瓶颈的那一段链缩短:
- 只改 `detect_register`
- 不碰百度、360、站长、爱站、时光机这些步骤
- 保留当前版的连接复用和失败闭环
- 只把注册检测改得更像老版本那种“先尽快打出去,再说”
### 这版模式做了什么
打开后,注册检测会变成下面这个思路:
- 即使全局 `allow_direct=false`,注册检测这一步也允许直连
- 默认先连续直连 `2`
- 这两次之间不再等代理补货
- 只有前面的直连没打通,后面才继续走代理兜底
简单说就是:
- 当前默认模式:代理优先,直连兜底
- 单机性能模式:注册检测直连优先,代理兜底
### 怎么开
这版先做成环境变量开关:
- `DOMAINCHECK_REGISTER_SINGLE_MACHINE_MODE=1`
- `DOMAINCHECK_REGISTER_DIRECT_STREAK_ATTEMPTS=2`
当前默认建议先用这组:
- `进程35`
- `线程900`
- `DOMAINCHECK_REGISTER_SINGLE_MACHINE_MODE=1`
- `DOMAINCHECK_REGISTER_DIRECT_STREAK_ATTEMPTS=2`
如果后面继续试更激进一点,再考虑把直连连打次数从 `2` 提到 `3`,但不建议一上来就抬。
### 为什么不是直接回退老版本
老版本真正有参考价值的是“链短、切得快”,不是它的实现本身更先进。
所以这次没有回退这几样东西:
- 没回退到 `20s * 3` 的长超时
- 没回退到“注册失败也继续往后跑”
- 没丢掉当前的 `Session` 复用
换句话说,这次是“借老策略”,不是“退老实现”。

View File

@@ -0,0 +1,405 @@
# 项目当前运行流程说明
> 说明:这份文档主要回答“项目现在怎么跑”。当前线上执行基线、观察顺序和是否继续调参,统一以 [当前线上最终Runbook.md](/www/wwwroot/getDomain/docs/当前线上最终Runbook.md) 为准。
更新时间:`2026-04-24`
## 这份文档是干什么的
这不是一份“理想设计稿”,而是按项目现在的真实跑法整理出来的说明。
目标只有一个:让人用人话看明白,这个项目现在到底怎么跑,任务从哪里来,谁在执行,结果又是怎么回来的。
## 先用一句话讲明白
这套系统现在不是“点一下开始,然后一台机器自己跑完”。
它现在的实际跑法是:
海外控制面先挑出要检测的域名,打成一批任务,然后把这批任务交给大陆节点;大陆节点把任务拉下来后,由 worker 一步一步去跑检测;跑出来的结果,再同步回海外控制面,最后由页面统一展示。
## 先认识 5 个角色
### 1. 海外控制面
可以把它理解成“总调度台”。
它主要负责:
- 创建检测任务
- 给大陆节点派动作
- 接收大陆回传的运行状态和检测结果
- 在页面上展示当前进度
当前本机 `overseas-control-01` 就是这个角色。
它本机一般不直接跑检测。
### 2. 大陆控制节点
可以把它理解成“大陆现场调度员”。
它主要负责:
- 去海外把待检测批次拉下来
- 启动大陆这边的 sync-agent、worker
- 把大陆现场的运行情况回传出去
### 3. 大陆 worker 节点
这才是真正“干活”的机器。
它负责:
- 领取待执行任务
- 开线程跑检测
- 把每一步的结果写回本地
### 4. sync-agent
它就是“搬运工”。
负责在海外和大陆之间搬 3 类东西:
- 待检测任务批次
- 当前运行状态
- 检测结果
### 5. node-agent
它就是“远程执行员”。
它会在节点上定时来领控制命令,然后执行,比如:
- 启动 worker
- 开始检测
- 拉取任务
- 重启服务
## 当前现场快照
下面这段,是按 `2026-04-24 01:44 - 01:45` 左右控制面看到的实时数据整理的。
- 当前控制面节点是 `overseas-control-01`
- 角色是 `overseas / control`
- 本机 API 在线
- 本机 worker 不承担实际检测
当时控制面看到的活动任务是:
- `job_id = 208`
- `job_code = sync-overseas-1430`
- 状态是 `running`
- 这一批一共 `5000` 个任务项
- 其中 `4435` 个已经被领取
- `575` 个正在执行
当时控制面汇总看到的执行规模大致是:
-`28` 个参与节点
-`28` 个检测进程
-`575` 个活跃线程
当时积压里最大头还是“注册状态检测”:
- `detect_register` 待处理约 `8235`
- 后续步骤待处理约 `1243`
同一时间段里,系统的 readiness 仍然提示:
- 大陆节点心跳并不稳定
- 还有结果批次待推送
这说明了一件很关键的事:
不是完全没跑,而是“链路在跑,但跑得不稳”。所以现在的主矛盾,确实还是稳定性,不是单纯把线程数继续往上调。
## 整个项目现在是怎么跑的
下面按真实流程,一步一步讲。
### 第 1 步:海外控制面先挑出“需要再检测”的域名
简单理解就是:
- 还没检测过的
- 之前检测失败过的
- 或者状态需要重新确认的
这些域名会先被挑出来,形成一份“待处理名单”。
这时候只是“选名单”,还没有真正开始跑检测。
### 第 2 步:海外控制面创建一轮检测任务
系统会先创建一条“本轮任务”记录。
然后把这批域名拆成很多个“任务项”。
这里要特别注意:
它不是一次就给某个域名下发“整套检测”。
它做的是:
- 先判断这个域名下一步该做什么
- 只给它安排“下一步”
所以这个系统现在跑的是“分步骤推进”,不是“单个域名一次性从头跑到尾”。
### 第 3 步:海外控制面自己不跑,而是把动作派到大陆
当你在页面上点“开始检测”后,海外控制面会去排队发送几类动作给大陆节点:
- 让大陆启动 sync-agent
- 让大陆去拉取待检测批次
- 让大陆启动 worker
- 让大陆开始执行检测
这些动作不是直接 ssh 过去硬敲命令。
而是先进入远程动作队列,再由大陆节点上的 node-agent 定时来领。
所以你可以把它理解成:
海外控制面负责“发指令”,大陆节点负责“取指令并执行”。
### 第 4 步:大陆控制节点先把任务批次拉下来
大陆控制节点会去海外控制面拿一批待检测域名。
拿到以后,会做几件事:
- 把这批域名落到大陆本地库里
- 在大陆本地创建一条对应的 job
- 给每个域名生成“下一步要做什么”的任务项
- 再回头告诉海外:这批任务我已经收到了
这个“确认收到”很重要。
因为如果不确认,海外会以为这批任务还没被接走,后面就可能重复下发。
另外,大陆控制节点不会无脑一直拉新任务。
如果它发现本地已经堆了很多待处理任务,它会先停一下,不再继续拉新批次,避免越堆越多。
### 第 5 步worker 启动后,先做准备,不会立刻开跑
worker 真正开始检测前,会先做一轮准备动作。
大致包括:
- 重新读取最新配置
- 重新读取线程数和进程数
- 重新加载 cookies
- 刷新代理池
- 回收上次异常退出留下来的遗留任务
所以你看到“worker 已经启动”,并不等于“已经开始稳定出结果”。
它中间还有一个准备阶段。
### 第 6 步worker 真正领的,是任务队列里的“下一步”
worker 现在不是直接扫整张域名表。
它主要是从任务队列里领取待执行任务。
领取后的状态大致可以这样理解:
- `pending`:还没被谁接手
- `claimed`:已经被某个节点领走了
- `running`:已经开始跑了
- `completed / failed / blacklisted`:这一小步跑完了
也就是说,系统现在盯的不是“这个域名整体做完没”,而是“这个域名现在跑到哪一步了”。
### 第 7 步:一个域名不是一次跑完,而是一小步一小步推进
当前默认的主流程顺序,大致是:
1. 注册状态检测
2. 百度 site 检测
3. 360 site 检测
4. 站长之家检测
5. 爱站检测
6. 时光机检测
有些来源的域名,会跳过注册状态这一步,直接从后面的步骤开始。
所以你不能把它理解成“所有域名一定都从第一步开始”。
更准确地说,是系统会先判断这个域名“现在最该补哪一步”。
### 第 8 步:流程顺序,和 worker 实际优先领什么,不完全一样
这个地方很容易误会,所以单独说一下。
流程顺序上,域名一般是先过前面的关,再去后面的关。
但 worker 实际领任务时,会优先照顾已经走到后面的域名。
为什么要这样做?
因为如果完全按最前面的步骤一直领,后面的域名会永远被堵住,怎么都跑不到尾部。
所以现在的真实策略更像是:
- 流程上按顺序推进
- 调度上优先让已经走到后面的域名尽快跑完
这也是为什么你现在会看到“注册状态检测积压很大”,但后面的步骤也还在继续跑。
### 第 9 步:每一步跑完后,系统会决定下一步怎么走
某一步做完后,系统不会简单地只记一个“成功 / 失败”。
它还会决定后面怎么走。
大致有几种情况:
- 这一步通过了:给这个域名创建“下一步”的任务项
- 这一步命中黑名单:后面的步骤就不再继续
- 这一步属于外部站点异常、超时、代理问题:可能会重试,也可能降级,也可能转成人工复核
- 这一步明确不通过:流程在这里终止
所以这个项目现在不是“跑完就完”,而是“每走完一步,再决定下一步”。
### 第 10 步:结果先写回大陆本地
worker 跑完某一步后,会先把结果写回大陆本地。
写回去的内容包括:
- 这一个任务项是什么结果
- 这个域名当前是什么状态
- 这一步的详细信息
- 是否需要人工复核
所以大陆本地库,先是“第一落点”。
### 第 11 步:大陆再把结果和运行状态同步回海外
大陆这边不是只回传“最终结果”。
它还会把两类信息往海外送:
- 当前运行状态
- 最近有哪些域名开始了、完成了、失败了、进黑名单了
然后海外控制面收到后,再去更新自己的展示和汇总。
这也是为什么页面上看到的数字,不是某一台机器的原始数字。
它其实是“控制面汇总后的结果”。
### 第 12 步:海外页面最后展示出来的,是一份汇总快照
所以现在页面上的数据,本质上是几部分拼起来的:
- 控制面自己知道的任务信息
- 大陆回传的运行状态
- 大陆回传的结果事件
- 当前 job 的统计汇总
这就会带来一个现实情况:
如果大陆心跳断一下,或者结果同步晚一点,页面看起来就会突然不稳定,甚至和现场有一点时间差。
这不一定代表 worker 完全没跑。
很多时候,只是“页面依赖的回传链路断了一下”。
## 你可以把现在的项目理解成两条并行链
### 第一条:任务执行链
海外选域名 -> 建任务 -> 大陆拉批次 -> worker 领任务 -> 按步骤推进 -> 写结果
### 第二条:运行状态和结果同步链
大陆上报运行状态 -> 大陆回推结果事件 -> 海外接收并汇总 -> 页面刷新展示
这两条链只要有一条不稳,使用感受就会变差。
如果任务执行链慢,你会感觉“没速度”。
如果同步链不稳,你会感觉“页面不准、看不清到底跑没跑”。
## 为什么你会一直觉得“没有速度、没有效率”
因为现在影响体验的不只是“worker 够不够快”。
这套系统至少要同时经过下面这些环节:
- 海外选任务
- 海外派动作
- 大陆领动作
- 大陆拉批次
- worker 领任务
- 外部站点检测
- 结果回传
- 页面汇总展示
任何一段不稳,最后给人的感觉都会像是“整套系统没跑起来”。
所以从当前现场来看,先把主链路稳定跑顺,仍然比继续往上冲性能更重要。
## 如果你只想用最简单的方法判断现在卡在哪
可以按下面这个顺序看:
1. 先看有没有新的活动 job
- 没有的话,说明卡在“任务还没真正建起来”
2. 再看大陆有没有把任务批次收下
- 没收下的话,说明卡在“海外发过去了,但大陆没接住”
3. 再看 `claimed``running` 有没有开始增长
- 不增长的话,说明 worker 没真正开始领任务
4. 再看 `completed / failed / blacklisted` 有没有开始变化
- 长时间不变的话,说明任务虽然在跑,但结果没持续产出
5. 最后看海外页面有没有跟着更新
- 大陆明明在跑,海外不更新,通常就是“结果同步链”有问题
## 最后只记住 4 句话就够了
1. 现在这套项目是“海外控制,大陆执行”,不是单机直跑。
2. 一个域名不是一次跑完,而是按步骤一小步一小步推进。
3. 页面看到的是汇总快照,不是某台机器的原始现场数字。
4. 当前最大的矛盾仍然是链路稳定,不是单纯把性能参数继续调大。
继续看完了,当前是稳推进,不是又卡死。
现在现场是:
- `idx_detect_job_items_release_node_job` 还没转 `valid=1`
- 但已经稳定在 `index validation: scanning table`
- 最新进度到:
- `229991 / 6197384`
- `watch` 这条后台链是活的,而且一直在连续记进度:
- `18:09:51 -> 126725`
- `18:10:52 -> 169564`
- `18:11:52 -> 210067`
- `18:12:22 -> 227856`
另外两点也正常:
- `876` 现在还显示 `running`
- worker 已经全恢复:
- `base=active`
- `templated=199`
- `node_agent=active`
一句话说:
**现在已经从“卡住”切成“后台验证扫描中”,而且进度在稳定往前走。**
下一步最合理的动作不是再人工乱动,而是继续让它扫;一旦索引转成 `valid=1``codex-job876-tail-watch.service` 就会自动接着去释放 `876` 的尾项。
发布 sync_push_service.py
重启 domaincheck-sync-agent.service
验证:
detect_result_projection push 是否恢复
detect_result_ingest 是否出现