# 测试优化分析 > 说明:这份文档保留作历史分析记录。当前线上执行基线与观察方法,统一以 [当前线上最终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` 复用 换句话说,这次是“借老策略”,不是“退老实现”。