Files
csctalon/docs/ops/数据采集-运维手册.md
tanzk 3f821560a1
Some checks failed
gitleaks / gitleaks (push) Successful in 7s
backend-test / frontend-lint-test (push) Has been cancelled
backend-test / backend-lint-test (push) Has been cancelled
fix(shared-doc): 用 Xvfb -ac 直启替代 xvfb-run 包裹,修正 xauth 阻塞 (#49)
生产端到端验证暴露首版实现的真实缺陷: 首版用 `xvfb-run -n 99 sleep infinity` 包裹,
xvfb-run 默认启用 xauth 保护, 而 chromium 是另一个进程拿不到该 cookie →
实测仍报 "Authorization required, but no authorization protocol specified"
+ "Missing X server or $DISPLAY"。

修正:
- 改为直接拉起 `Xvfb :99 -screen 0 1280x800x24 -ac -nolisten tcp`
  (-ac 关闭访问控制; 同容器内使用且不开 TCP, 不扩大暴露面);
- 判据由「socket 文件存在」改为「socket 可连接」—— X server 被杀后 socket 文件
  会残留(内核不自动删), 只看存在会把死服务当活的;
- 导出 XAUTHORITY 占位文件, 消除 X 客户端 "~/.Xauthority does not exist" 告警;
- 等待上限 2s → 5s(容器冷启动更稳)。

新增回归用例 test_xvfb_started_with_ac_to_avoid_xauth_error 锁定启动参数防回退。
文档同步更新 ops 手册 §七 与架构提案的取舍/风险/测试三节。
2026-09-13 12:15:33 +08:00

15 KiB
Raw Permalink Blame History

数据采集运维手册(故障排查与缺口恢复)

生效日期:2026-09-03 适用范围:BMS / ai-acc / SCRM 定时同步的日常巡检、故障定位、数据缺口恢复 关联规范:数据同步与加工说明.md、issue-workflow.md、alembic-migration管理规范.md

背景:2026-09-02 cs_records 接待数据缺口事件(14:00–24:00 缺约 3500 条)暴露三类问题:① 上游 500 被当确定失败且熔断后不探活;② 缺口无自愈;③ 报错不清晰、无骤降告警。本手册把「怎么发现、怎么定位、怎么恢复、怎么防复发」固化成可执行动作。


一、核心原则

  1. 先确认范围,再动手恢复:先查缺口在哪张表、哪个日期、哪个时段,再决定用哪种补跑方式,避免整窗重拉覆盖历史。
  2. 事故先建工单(incident),恢复动作记录到工单,治理缺陷另建 governance 工单。
  3. 只读优先:排查阶段一律只读查询,恢复性写操作需运维授权后再执行。
  4. 时间一律北京时间:所有业务时间按 AT TIME ZONE 'Asia/Shanghai' 口径判断。

二、日常巡检(每天 1 次,5 分钟)

2.1 看同步任务状态

进入运营后台「数据中心 → BMS 同步」,或调接口:

# 定时同步状态(含熔断标记 halted_types)
curl -s http://<host>/api/v1/fetch/schedule/status -H "Authorization: Bearer <token>"

# 最近同步日志
curl -s "http://<host>/api/v1/fetch/sync-logs?page=1&page_size=20" -H "Authorization: Bearer <token>"

关注点:

  • is_halted=true → 有专项被熔断,立即看 halted_types 是哪个专项。
  • 最近一条日志 status != success → 记下 data_type、error、created_at,转第二节定位。

2.2 看每日入表量(防断层)

按天统计各业务表入表量,与近 7 日均值对比。以接待表为例:

-- 近 7 天 cs_records 每日接待量(北京时间)
SELECT
  (operation_time AT TIME ZONE 'Asia/Shanghai')::date AS d,
  count(*) AS cnt
FROM cs_records
WHERE operation_time >= now() - interval '7 days'
GROUP BY d
ORDER BY d;

判定:某日 cnt 较前 7 日均值骤降 > 30%(或低于历史低点),即视为数据缺口,进入第三节定位。

⚠️ 本项目告警中心目前未注册 cs_records 接待量指标,断层不会自动告警,必须靠此人工巡检兜底(见第五节待建项)。


三、故障定位(按顺序排查)

3.1 看同步日志的错误码与文案

-- 最近 24h 失败/部分成功的同步
SELECT created_at, data_type, trigger_type, status, error, total_rows, inserted_rows
FROM bms_sync_logs
WHERE created_at >= now() - interval '24 hours'
  AND status IN ('failed','partial')
ORDER BY created_at DESC
LIMIT 50;

错误类型速查:

error 关键词 含义 是否可自动恢复
HTTP 500 / 返回 JSON 而非文件流 上游服务端错误(本次 09-02 根因) 上游恢复后可补跑
请求超时 / TimeoutException 上游响应慢/超时 降低时间窗口后补跑
连接失败 / ConnectError 网络/上游不可达 网络恢复后补跑
登录失败 / 响应无 token 账号/凭证失效 需更新凭证,不能自动恢复
账号/密码未配置 同步设置页未填凭证 需人工配置

3.2 判断缺口时间窗

  1. 从日志找到最后一条 success 的 end_time(北京时间);
  2. 缺口 = 该 end_time 之后 → 当前时刻,或按业务日 00:00–24:00 分段;
  3. 对照上游平台(ai-acc)确认该时段数据是否真实存在,避免误补。

3.3 判断是否熔断

  • 调度状态 is_halted=true → 该专项当天剩余自动同步已停;
  • 手动触发一次同步或调 /schedule/start 会清除当天熔断标记。

四、缺口恢复(写操作,需授权)

4.1 首选:单日指定范围补跑

# 手动提交指定日期的 cs_record 同步(避开 7 天整窗,成功率更高)
curl -s -X POST http://<host>/api/v1/fetch/task/submit \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "data_types": ["cs_record"],
    "time_range": "custom",
    "custom_start": "2026-09-02 00:00:00",
    "custom_end": "2026-09-02 23:59:59"
  }'

为什么选单日:ai-acc 单次导出上限 7 天,大范围导出易触发上游 500;单日窗口更稳。

4.2 备选:依赖定时兜底

  • 5:00 前一天补跑:自动补跑前一天全部专项(不受熔断影响);
  • 8:00 近 7 天兜底:覆盖滞后 1~2 天的数据回填;
  • 若单日手动补跑失败,可等 次日 8:00 的 7 天兜底 再次覆盖该日期(前提上游已恢复)。

4.3 恢复后验证

-- 验证补跑后该日数据量是否回到正常区间
SELECT count(*) FROM cs_records
WHERE (operation_time AT TIME ZONE 'Asia/Shanghai')::date = '2026-09-02';
  • 对比正常基线(~5000–6000 条/日),确认缺口已回填;
  • 将验证结果截图/记录回填到事故工单,作为闭环证据。

4.4 上游恢复确认

  • 登录 ai-acc 平台,手动小范围导出一次测试数据;
  • 确认返回 xlsx 文件流(非 JSON 错误),再执行批量补跑。

五、已知待建能力(防复发)

以下为设计/代码层改进建议,已登记为治理工单 tanzk/csctalon#2,未排期前靠人工巡检兜底。

# 改进项 现状 目标
1 错误分级 + 重试退避 5xx/JSON500 被当确定失败,仅单次重试 5xx 视为可重试,指数退避
2 按日切片降级重试 7 天整窗失败即整批丢弃 整窗失败 → 按日切片重试
3 熔断 + 半开探测 一次失败即熔断,当天不再探活 滑动窗口连续 N 次才熔断,半开探测恢复
4 缺口待补队列 补跑只覆盖前一天 记录「业务日期+专项」缺口,次日强制回补
5 cs_records 接待量骤降告警 无该指标 注册指标 + 阈值告警

六、快速参考:本次 09-02 事件处理复盘

  1. 发现:运维对比近 7 日 cs_records 数据量,09-02 仅 1699 条(正常 5000–6000)。
  2. 定位:同步日志显示 ai-acc 大范围导出返回 HTTP 500,熔断后当天未再探活。
  3. 恢复:上游恢复后手动指定 09-02 单日补跑;或等次日 8:00 的 7 天兜底覆盖。
  4. 闭环:事故工单 #1 记录补跑结果;治理工单 #2 排期改进项。

七、共享文档采集:扫码登录与 Xvfb 排查

性质:参考(REF)—— 现状记录 + 故障处置手册,无约束力,以代码为准。 适用范围:共享文档在线采集(腾讯文档 / 金山文档)的登录态相关故障。 关联工单:#45(页面内扫码登录,已交付)· #49(本链路部署实操验收)· #48(会话部署约束)

7.1 两条链路,先分清是哪一条

链路 A:页面内扫码(推荐) 链路 B:CLI 脚本降级
入口 接入中心 →「① 登录态」→ 某源「扫码登录」;或 数据采集 → 共享文档采集 →「登录」 本机跑 scripts/tencent_doc_login.py / kdocs_login.py
运行位置 生产后端容器内(需虚拟显示) 本机 macOS(有 GUI,无需 Xvfb)
前置条件 容器内 Xvfb 能跑起来 本机有仓库代码 + Python 环境
产物 /app/data/browser_states/<platform>.json 同上,需手工上传到宿主机

产物路径口径:容器内 /app/data/browser_states/ ↔ 宿主机 /opt/csctalon/browser_states/(由 docker-compose.yml 挂载)。 宿主机目录属主特殊,必须走 sudo mv(不能直接 scp 覆盖,详见 7.4)。

7.2 故障:点「扫码登录」报 Missing X server or $DISPLAY

报错原文(浏览器提示条 / 接口 502):

BrowserType.launch: Target page, context or browser has been closed
Looks like you launched a headed browser without having a XServer running.
Set either 'headless: true' or use 'xvfb-run <your-playwright-app>' ...
[err] Missing X server or $DISPLAY

含义:页面内扫码需要「有头浏览器」(要渲染二维码图片),而有头浏览器必须有 X 显示服务。 生产后端容器没有物理显示器 → 必须由 Xvfb 提供虚拟 DISPLAY。

一分钟定位(三条命令,全部只读):

# ① 容器里到底有没有 Xvfb / xvfb-run(装了没)
ssh <服务器> "docker exec csctalon-backend sh -c 'command -v Xvfb; command -v xvfb-run'"

# ② 当前有没有可用的显示(跑着没)—— 关键看 DISPLAY 是否为空
ssh <服务器> "docker exec csctalon-backend sh -c 'echo DISPLAY=[\$DISPLAY]'"

# ③ X socket 是否真的存在(Xvfb 进程是否活着)
ssh <服务器> "docker exec csctalon-backend sh -c 'ls -l /tmp/.X11-unix/ 2>&1'"

判读表:

① command -v ② DISPLAY ③ socket 结论 处置
有 为空 无 装了包但没人启动进程(2026-09-13 生产实际形态) 见 7.3(已自愈,直接重试即可)
有 有值 存在 正常 若仍报错,看后端日志是不是别的原因
有 有值 不存在 DISPLAY 是陈旧/残留环境变量 见 7.3
无 — — 镜像没装 xvfb 重新构建镜像(backend/Dockerfile 已含 xvfb xauth)

⚠️ 最易踩的误区:command -v xvfb-run 有输出 ≠ 可用。 它只说明包装了,不代表进程起了。2026-09-13 的线上故障就是代码按「装了就算可用」判定, 于是跳过了 501 降级、直接启动浏览器炸掉,用户看到一大坨 Playwright 原始日志。 判据必须是「socket 在不在」和「DISPLAY 有没有值」,不是 which。

7.3 处置:显示服务现在会「按需自动拉起」

2026-09-13 起,后端在真正要启动浏览器之前(点「扫码登录」那一刻)会:

  1. 检查 $DISPLAY 指向的 X socket 是否真的存在;
  2. 不存在且镜像里有 xvfb-run → 自动拉起 Xvfb :99 并注入 DISPLAY(幂等,已起则复用);
  3. 拉不起来才返回 501,并提示用「命令行」按钮走链路 B。

因此:容器装了 xvfb 包(本仓库默认镜像已装)时,直接重试「扫码登录」即可,无需人工登服务器起进程。 确认自愈是否生效:

# 点一次「扫码登录」后执行 —— 应看到 DISPLAY 有值 且 socket 存在
ssh <服务器> "docker exec csctalon-backend sh -c 'echo DISPLAY=[\$DISPLAY]; ls /tmp/.X11-unix/'"

设计取舍(为什么不常驻 Xvfb):常驻要多一个进程长期占内存;按需拉起零常驻开销, 代价是首次点「扫码登录」多约 1 秒。后端是单 worker,极端情况下自我修复会短暂占用该 worker(上限约 5 秒)。

⚠️ 实现要点(踩过的两个坑,改代码时别退回去):

  1. 必须用 Xvfb :99 -ac,不能用 xvfb-run 包裹 —— xvfb-run 默认启用 xauth 保护, 而 chromium 是另一个进程拿不到那个 cookie,会报 Authorization required, but no authorization protocol specified + Missing X server or $DISPLAY, 浏览器照样起不来(2026-09-13 端到端验证就是这样被卡住的)。-ac 关闭访问控制; 该 X 仅本机同容器使用(同时 -nolisten tcp 不开端口),不扩大暴露面。
  2. 判据必须"能连上 socket",不能只看 socket 文件是否存在 —— X server 被杀后 socket 文件会残留(内核不自动删),只看存在会把死服务当活的。查进程则容器内无 ps。

为什么不改用无头浏览器(headless: true)绕开:无头模式下拿不到可供人工扫的二维码截图, 而且腾讯/金山对无头浏览器有识别风险 —— 这条链路的前提就是「人来扫码」,必须真实渲染。

7.4 链路 B:命令行降级(页面内扫码彻底不可用时)

不要在生产容器里跑登录脚本 —— 容器内既没有 scripts/ 也没有 .venv,路径是假的。 正确做法是两段式(本机生成 → 上传):

# ① 本机(macOS 有 GUI)生成登录态; 脚本会弹浏览器, 扫码后回终端按回车保存
cd backend && ./.venv/bin/python ../scripts/tencent_doc_login.py --url "<共享文档地址>"
#   金山文档: ../scripts/kdocs_login.py
#   产物: backend/data/browser_states/tencent_doc.json

# ② 上传到生产宿主机(先传 /tmp, 再 sudo mv —— 目标目录属主特殊, 直传会失败)
scp backend/data/browser_states/tencent_doc.json tanzk@<服务器>:/tmp/
ssh tanzk@<服务器> "sudo mv /tmp/tencent_doc.json /opt/csctalon/browser_states/"

页面上「命令行」按钮复制的就是这个两段式命令(命令文本由后端 GET /api/v1/sync/shared-docs/cli-fallback 下发,避免前后端各写一份漂移)。

7.5 登录态有效期与健康度

  • 健康度页面:接入中心 →「① 登录态」,展示各平台 state 文件的 mtime / 年龄。
  • 超过 7 天仅提示「建议重新登录」,不阻断采集 —— 各平台 token 实际有效期不同,判不准。
  • 同一平台同时只允许一个登录会话(并发扫码会互相覆盖登录态文件)→ 第二个会话返回 409。
  • 登录会话 TTL 600 秒;超时会话回收并关闭浏览器(防 chromium 进程泄漏,单实例 200–400MB)。

7.6 验证修复是否生效(验收动作)

# 1. 容器内能看到 Xvfb 按需拉起(X socket 出现)
ssh <服务器> "docker exec csctalon-backend sh -c 'ls /tmp/.X11-unix/'"

# 2. 页面实操: 接入中心 → ① 登录态 → 对腾讯源点「扫码登录」
#    期望: 弹窗中出现登录页截图(非空) → 扫码 → 点「我已扫码完成」→ 提示 cookies 条数

# 3. 落盘产物存在且是刚写的
ssh <服务器> "ls -l /opt/csctalon/browser_states/tencent_doc.json"

# 4. 关闭弹窗后无残留 chromium(会话资源已回收)
ssh <服务器> "docker exec csctalon-backend sh -c 'ls /proc | grep -c ^[0-9]' "   # 看进程数是否回落

八、禁忌

  1. 禁止在未确认缺口范围时直接全量重拉 7 天窗口(易再次触发 500 且覆盖历史)。
  2. 禁止用 incremental 补跑历史缺口(增量只接续上次成功点,不覆盖历史断层)。
  3. 禁止跳过只读排查直接对生产数据做写操作。
  4. 禁止在工单外口头完成恢复而不留痕。