# 16 domainCheck 多机检测与跨地域部署设计 ## 一、目标 本文档用于固化 `domainCheck` 的正式多机落地方案,满足下面这些约束: - Web 后台、管理 API 必须部署在国外机器 - 检测执行、代理、Redis、运行态存储部署在大陆机器 - 最终结果、配置、归档、备份以国外机器为主 - 初期部署不复杂,支持从 `国外 1 台 + 大陆 1 台` 起步 - 后期不够时,优先横向增加大陆 Worker,不推翻整体架构 一句话原则: > 国外负责“管、看、存、备份”,大陆负责“跑、调度、抢任务、贴近代理资源”。 ## 二、当前落地状态 截至 `2026-04-16`,第一阶段多机运行骨架已经完成,当前代码已具备: - 控制面节点注册与心跳 - 执行面节点注册与心跳 - 运行库骨架表自动初始化 - 集群节点快照接口 - 国外/大陆两套部署脚手架 当前已落地的运行态表: - `detect_worker_nodes` - `detect_jobs` - `detect_job_items` - `detect_run_events` - `detect_sync_records` 当前可直接验证的接口: - `GET /api/v1/runtime/status` - `GET /api/v1/runtime/readiness` - `GET /api/v1/runtime/cluster` - `GET /api/v1/detect/job/active` - `GET /api/v1/detect/jobs` - `GET /api/v1/detect/queue-summary` 当前 `runtime/cluster` 还补充了 `summary` 摘要,至少会返回: - `status_counts` - `role_counts` - `region_counts` - `online_worker_nodes` - `online_control_nodes` - `busy_nodes` - `stale_nodes` - `offline_nodes` 当前还补充了同步观测骨架: - `GET /api/v1/runtime/sync-summary` - `GET /api/v1/runtime/sync-records` 可用于提前观察: - 当前节点是否启用同步推送 - 源地域 / 目标地域配置是否正确 - `detect_sync_records` 最近状态分布 当前代码还补了一层“运行态自动投影”: - 控制面在读取 `runtime/status` 时,会把当前运行摘要按去重策略写入 `detect_sync_records` - 当前记录类型为: - `runtime_projection` - 当前还新增了“结果摘要投影”: - `detect_result_projection` - 用于把当前活跃任务的摘要、最近阶段事件、任务收敛进度先沉淀到统一同步面板 - 其作用不是替代后续真正的 `sync-service` - 而是先保证跨地域部署阶段已经有统一可观察的“同步记录面板” 当前还补上了正式同步最小闭环骨架: - 大陆执行面可通过 `SYNC_TARGET_API_BASE_URL` 指向国外控制面 - 若 `SYNC_PUSH_ENABLED=true`,应由大陆 `domaincheck-sync-agent` 按轮询间隔自动推送最新运行态投影 - 国外控制面通过: - `POST /api/v1/runtime/sync-ingest` 接收投影 - 若配置了 `SYNC_SHARED_TOKEN`,接收端会校验: - `X-Domaincheck-Sync-Token` 因此当前虽然还没有完整业务结果同步服务,但“同步协议、发送端、接收端、记录面板、独立同步代理”都已经有了第一版可运行骨架 当前同步骨架已经支持的记录类型包括: - `runtime_projection` - `runtime_ingest` - `detect_result_projection` - `detect_result_ingest` - `runtime_push` 当前这一轮还补上了“结果批次同步可观测”能力: - `GET /api/v1/runtime/sync-summary` 现在会额外返回: - `detect_result_batches` - 可直接按最近检测任务查看: - 是否已生成结果投影 - 是否已推送 - 是否已被目标地域接收 - 最近一次失败原因 - 当前同步策略也已细化为: - `runtime_projection` 只推最新一条,避免把历史运行态投影整批补传 - `detect_result_projection` 按批次补推,确保检测结果积压可以自动追平 - 大陆 `controller` 的环境模板也已固定为: - `NODE_ROLE=control` 避免把 controller 误注册成普通 Worker,导致运行中心和 sync-agent 承载判断失真 当前这一轮还额外补齐了“检测会话追踪”能力: - `POST /api/v1/detect/start` 会生成本轮 `cycle_token` - `cycle_token` 会进入 Worker 控制指令 - Worker 心跳元数据会回写 `cycle_token / job_id / job_code` - `domain_started / domain_completed / domain_failed` 事件会附带 `cycle_token` - `GET /api/v1/detect/job/active` 已支持按当前 `cycle_token` 聚合“本轮事件” 当前运行态还补齐了“代理执行语义”: - 代理可用时显示 `代理正常` - 代理暂时不可用但允许直连时显示 `降级直连` - 代理暂时不可用且不允许直连时显示 `等待代理` - 当代理源全部 `HTTP 200` 但 `raw_items=0` 时,会额外标记为“供应池为空”,避免误判成程序异常 当前阶段的准确表述应为: > 多机运行骨架已经落地,分布式调度与跨地域结果同步进入下一阶段。 截至当前这一轮代码,最小任务闭环也已经接上: - `POST /api/v1/detect/start` 会先创建 `detect_jobs` - Worker 会优先从 `detect_job_items` 领取任务 - 运行中心已能显示当前活跃任务摘要 - 节点状态里已能反映 `busy / current_load` - Worker 已支持小批量领取、任务续租、过期租约回收 - API 控制面节点已支持定时心跳,避免集群视图把控制面误判为离线 - 活跃任务接口已支持节点分布、进度百分比和最近事件回看 - Worker 进程重启后会主动回收当前节点遗留的 `running / claimed` 运行态,避免后台出现“进程已重启但旧任务仍显示运行中”的残影 这意味着后续新增第二台大陆 Worker 时,已经不再是“完全从零设计”的状态,而是可以在现有骨架上继续细化调度策略。 当前最小联调命令也已经固定为: ```bash cd /opt/domaincheck/domain-api bash deploy/multi-region/check_cluster.sh http://127.0.0.1:8100 ``` 这条命令会连续检查: - `/health` - `/api/v1/runtime/cluster` - `/api/v1/detect/status` - `/api/v1/detect/job/active` 如需单独检查“跨地域同步骨架是否已接上”,建议再补两条: ```bash curl http://127.0.0.1:8100/api/v1/runtime/sync-summary curl http://127.0.0.1:8100/api/v1/runtime/sync-records?limit=10 ``` 如需快速拿到“当前这套多机部署是否已进入可联调 / 待处理 / 阻断”结论,当前也已经固定了: ```bash curl http://127.0.0.1:8100/api/v1/runtime/readiness ``` 该接口会直接给出: - `status` - `ready` - `attention` - `blocking` - `summary` - `blocking_issues` - `warnings` - `info` 如果这两条接口已经能看到: - `source_region / target_region` - `records_total > 0` - 最近记录里出现 `runtime_projection` 则说明当前测试服已经具备: - 同步配置观测入口 - 同步记录落库入口 - 后续接入正式 `sync-service` 的最小运行骨架 如果要在大陆 controller 节点本机确认“这台机器本身是否配成了 controller,而不是普通 Worker”,当前也已经固定了一条本机检查命令: ```bash cd /opt/domaincheck/domain-api bash deploy/multi-region/check_mainland_controller.sh ``` 这条命令会直接校验: - `/etc/default/domaincheck-worker` - `NODE_ROLE=control` - `SYNC_PUSH_ENABLED=true` - `SYNC_TARGET_API_BASE_URL` 是否已填写 - `domaincheck-worker` - `domaincheck-sync-agent` 是否已经启动 如果当前客观条件下还不能把代码真正部署到大陆机器,当前也支持先在国外测试机上做“模拟多节点联调”: ```bash cd /opt/domaincheck/domain-api bash deploy/multi-region/simulate_multi_region.sh http://127.0.0.1:8100 ``` 这条命令会临时模拟: - 一个大陆 `controller` - 一个大陆 `worker` 它的目的不是替代真实部署,而是先把下面这些提前跑通: - `runtime/readiness` - `runtime/cluster` - 运行中心的多机就绪度提示 - 文档、脚本、状态面板三者是否一致 如果测试服之前已经跑过旧节点、旧 worker,当前还残留陈旧心跳记录,也可以先清一次 `detect_worker_nodes` 里的历史残影,再观察 readiness: ```bash cd /opt/domaincheck/domain-api bash deploy/multi-region/prune_cluster_nodes.sh --minutes 30 --dry-run bash deploy/multi-region/prune_cluster_nodes.sh --minutes 30 ``` 它的作用是: - 清掉很久没心跳的旧节点记录 - 避免运行中心长期被历史离线节点污染成 `attention` - 让单机模拟多节点联调时,看到更接近真实接入后的状态 ## 三点五、当前实测结论 截至 `2026-04-16 18:56`,测试服已经验证通过: - `domaincheck-api` 与 `domaincheck-worker` 可在 `systemd` 下稳定重启 - `worker_mode=linux-systemd` - `runtime/cluster` 可同时看到: - `overseas-control-01` - `mainland-worker-01` - 大陆 Worker 节点元数据已显示: - `phase` - `detail` - `job_id` - `job_code` - `cycle_token` - `detect/job/active` 的 `current_cycle_events` 已只回看当前这一轮检测事件,不再混入旧轮次历史 当前测试服最新一轮实测 `cycle_token` 示例: - `aec105737f` 该轮已确认能看到: - `job_dispatch_requested` - `job_dispatch_sent` - `domain_started` - `domain_completed` 这说明“控制面发起一次检测”到“执行面逐域名回写事件”的会话闭环已经打通。 也说明当前后台已经不再只会显示“有告警”,而是能明确说明任务到底是在正常代理、降级直连,还是被代理卡住。 ## 四、推荐架构 ### 1. 国外控制面 国外机器负责: - `domain-web` - `domain-api` - 国外主 PostgreSQL - 配置中心 - 导出、审核、运营后台 - 最终结果归档 - 国外备份与日志归档 国外控制面的职责: - 对外提供访问入口 - 保存最终业务结果 - 保存系统配置和审计记录 - 接收大陆执行面的结果同步 - 为后续灾备提供稳定的权威数据源 ### 2. 大陆执行面 大陆机器负责: - `detect-worker` - 大陆 Redis - 大陆运行库 PostgreSQL - 代理池配置与网络环境 - 本地运行日志 - 调度和任务租约 大陆执行面的职责: - 抢任务 - 续租 - 运行检测链路 - 写入本地运行态 - 汇总并同步结果到国外 ### 3. 初期物理部署 当前最优起步方案: - 国外 `1` 台: - `domain-web` - `domain-api` - `postgresql_main` - 大陆 `1` 台: - `redis` - `postgresql_runtime` - `scheduler` - `detect-worker` 说明: - 这个起步方案的优先目标是先把跨地域职责边界定清楚 - 大陆机器当前可以先把 Redis、运行库、调度和 Worker 放在同一台 - 但逻辑上必须按不同角色设计,避免后续扩容时重新返工 - 各节点身份建议统一通过 `/etc/default/domaincheck-api` 与 `/etc/default/domaincheck-worker` 管理,不直接修改 service 文件正文 ### 4. 后续扩容 当大陆执行压力不够时,扩容顺序建议为: 1. 新增大陆 Worker 机器 2. 如果 Redis 压力增大,再拆 Redis 3. 如果运行库写入压力增大,再拆运行库 4. 国外主库继续作为最终结果主库,不参与高频任务调度 后续结构示意: ```text 国外 ├── web + api + postgresql_main └── backup / archive 大陆 ├── redis + postgresql_runtime + scheduler ├── worker-01 ├── worker-02 ├── worker-03 └── worker-N ``` ## 五、数据分层 ### 1. 结果态数据 这类数据保存在国外主库: - 域名主数据 - 检测最终结果 - 黑名单结论 - 导出记录 - 审核记录 - 系统配置 - 操作审计 特点: - 最终一致 - 生命周期长 - 提供给后台页面和运营使用 ### 2. 运行态数据 这类数据优先保存在大陆执行面: - 任务领取状态 - 任务租约 - 节点心跳 - 当前阶段 - 实时日志事件 - 重试信息 - 本地暂存结果 特点: - 高频读写 - 延迟敏感 - 主要服务于 Worker 协作和调度 ## 六、任务模型 当前项目还偏向单机 Worker 直接扫描 `domains` 表的模式。正式多机化后,建议改为显式任务模型。 ### 1. 核心表建议 建议新增: - `detect_jobs` - `detect_job_items` - `detect_worker_nodes` - `detect_run_events` - `detect_sync_records` ### 2. detect_jobs 表示一轮检测任务。 建议字段: - `id` - `job_code` - `source` - `plan_hash` - `status` - `created_at` - `started_at` - `finished_at` - `created_by` - `remark` 说明: - `plan_hash` 用于标识本轮检测所使用的规则版本和参数快照 - 一轮任务可以对应大量域名任务项 ### 3. detect_job_items 表示单个域名的具体任务项。 建议字段: - `id` - `job_id` - `domain_id` - `status` - `claimed_by` - `claim_token` - `lease_expires_at` - `attempt_count` - `last_error` - `started_at` - `finished_at` - `result_version` - `updated_at` 建议唯一约束: - `unique(job_id, domain_id)` 说明: - 多台 Worker 只抢 `status=pending` 且未被占用或租约已过期的任务 - `claim_token` 用于避免状态回写串单 - `lease_expires_at` 用于机器故障后的任务回收 ### 4. detect_worker_nodes 表示 Worker 节点。 建议字段: - `node_code` - `region` - `role` - `hostname` - `ip` - `status` - `worker_version` - `last_heartbeat_at` - `current_load` - `remark` 说明: - 后台后续可以直接展示“哪个节点在线、哪个节点在跑、当前负载多少” ### 5. detect_run_events 表示高价值阶段日志。 建议字段: - `id` - `job_id` - `job_item_id` - `node_code` - `event_type` - `level` - `message` - `payload_json` - `created_at` 说明: - 页面展示优先依赖事件流,不直接硬依赖某一台机器本地日志文件 - 原始大日志仍保留在大陆 Worker 本地 ## 七、多机抢任务与去重 ### 1. 抢任务方式 推荐使用数据库租约模型: - Worker 从 `detect_job_items` 中领取一批待处理任务 - 使用 `FOR UPDATE SKIP LOCKED` 或原子更新方式抢占 - 领取后写入: - `claimed_by` - `claim_token` - `lease_expires_at` ### 2. 去重原则 多机环境下,去重不能依赖“约定不重复”,必须依赖数据库约束和租约。 至少保证: - 同一轮 `job_id` 下,同一域名只出现一条任务项 - 同一任务项同一时刻只允许被一个 `claim_token` 持有 - 回写结果时校验 `claim_token` ### 3. 续租机制 Worker 执行过程中应定期续租: - 每 `30-60` 秒续租一次 - 续租时更新 `lease_expires_at` - 如果节点挂掉,租约到期后其他 Worker 可重新领取 当前代码已实现的原则是: - 任务被领取后进入 `claimed / running` - 检测步骤执行过程中会持续续租 - 若任务长期未续租且租约过期,会自动回收到 `pending` 这样可以避免: - 长任务跑到一半租约失效 - Worker 异常退出后任务永远卡死 - 后续多机扩容时出现大量“假占用” ### 4. 失败重试 建议对失败进行分类: - 网络失败 - 代理失败 - 第三方目标站异常 - 程序逻辑失败 可按类型控制重试次数,不要无限重试。 ### 5. 当前代码与目标模型的关系 当前代码里,运行态表和节点心跳已经具备,但主检测流程仍以现有 `domains` / `detect_status` 链路为主。 这意味着: - 现在已经可以先把“多机可见性”和“节点骨架”跑起来 - 下一步要把“检测入口”从直接扫 `domains`,逐步切到 `detect_jobs + detect_job_items` - 切换时必须保留旧链路回退能力,避免一次性重写导致现网不稳 ## 八、同步策略 ### 1. 国外到大陆 同步内容: - 系统配置 - 检测规则 - 敏感词版本 - 任务创建命令 特点: - 低频 - 要求可靠 - 可版本化 ### 2. 大陆到国外 同步内容: - 任务进度摘要 - 关键阶段事件 - 最终检测结果 - 黑名单结论 - 归档日志包索引 特点: - 高频部分只传摘要和关键事件 - 最终结果必须幂等 ### 3. 幂等要求 结果同步到国外时,必须支持幂等: - 同一 `job_item_id` 重复上报不应产生重复结果 - 同一域名同一轮任务只保留最终有效状态 ### 4. 推荐同步落地方式 第一阶段不建议直接做数据库双向复制,更推荐独立同步服务: - 大陆执行面负责写本地运行态 - `sync-service` 负责批量汇总结果与事件 - 国外控制面负责接收、落库、归档 推荐第一阶段同步内容: - 最终检测结果 - 关键阶段事件 - 失败摘要 - 节点健康摘要 - 配置快照版本 不建议第一阶段同步: - 全量原始日志 - 高频心跳明细 - 临时抓取中间产物 原因: - 成本高 - 跨地域网络抖动会放大耦合 - 对运营后台价值不成比例 ## 九、部署建议 ### 1. 国外机器 目录建议: ```text /opt/domaincheck ├── domain-api ├── domain-web └── shared ``` 部署角色: - `web` - `api` - `postgresql_main` ### 2. 大陆调度中心 目录建议: ```text /opt/domaincheck ├── domainCheck ├── runtime-db ├── redis └── shared ``` 部署角色: - `scheduler` - `redis` - `postgresql_runtime` - `worker` ### 3. 大陆纯 Worker 节点 目录建议: ```text /opt/domaincheck ├── domainCheck └── shared ``` 部署角色: - `worker` ### 4. 一键化原则 部署脚本必须满足: - 新机器只需要少量环境变量 - 不要求现场手工改很多路径 - 国外机和大陆机各自有独立入口脚本 - 后续新增大陆 Worker 节点继续复用同一脚本 ### 5. 当前建议的最小部署 当前最适合正式起步的形态: - 国外 `1` 台: - `domain-web` - `domain-api` - 国外主 PostgreSQL - 大陆 `1` 台: - `redis` - 大陆运行库 PostgreSQL - `detect-worker` - 后续可补 `sync-service` 扩容时优先: 1. 新增大陆 Worker 2. 再视压力拆分大陆运行库与 Redis 3. 国外控制面保持稳定,不跟着高频扩缩 ## 十、阶段性落地建议 ### 第一阶段 先完成: - 国外控制面部署 - 大陆单机执行面部署 - 节点注册与心跳 - 当前后台继续可用 ### 第二阶段 再完成: - 任务项模型 - 多 Worker 抢任务 - 事件日志流 - 结果异步同步回国外 ### 第三阶段 最后完成: - 大陆多 Worker 横向扩容 - 运行库与调度中心独立拆分 - 国外归档和监控补齐 ## 十一、运行与观测建议 多机之后,不能再只靠“登录某台机器 tail 日志”来判断系统状态。 建议分层: - 大陆 Worker 本地保留完整原始日志 - `detect_run_events` 保存关键阶段事件 - 国外后台优先展示事件流、节点负载和同步状态 - 真排障时再下钻到具体节点原始日志 后台建议优先展示: - 当前在线节点数 - 每节点最近心跳 - 每节点当前负载 - 当前活跃任务数 - 队列积压与最老待领年龄 - 租约是否过期、是否即将到期 - 近 15 分钟每节点吞吐 - 最近失败摘要 - 最近同步结果 - 最近结果摘要投影 ## 十二、下一阶段开发清单 接下来建议按下面顺序继续落地: 1. 后台创建 `detect_jobs / detect_job_items` 2. Worker 实现批量 claim / renew / finish / fail 3. 增加租约超时回收 4. 将检测阶段事件写入 `detect_run_events` 5. 增加 `sync-service` 6. 完成第二台大陆 Worker 接入演练 ## 十三、最终建议 对当前项目来说,最优解不是“全部放国外”,也不是“全部放大陆”,而是: - 国外做控制面和主结果库 - 大陆做执行面和运行态调度 - 先从 `国外 1 台 + 大陆 1 台` 起步 - 后续优先横向增加大陆 Worker 一句话总结: > 先把职责边界设计对,再让部署简单;后续扩容时只加 Worker,不再重构核心架构。