生产端到端验证暴露首版实现的真实缺陷: 首版用 `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 手册 §七 与架构提案的取舍/风险/测试三节。
15 KiB
数据采集运维手册(故障排查与缺口恢复)
生效日期: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 被当确定失败且熔断后不探活;② 缺口无自愈;③ 报错不清晰、无骤降告警。本手册把「怎么发现、怎么定位、怎么恢复、怎么防复发」固化成可执行动作。
一、核心原则
- 先确认范围,再动手恢复:先查缺口在哪张表、哪个日期、哪个时段,再决定用哪种补跑方式,避免整窗重拉覆盖历史。
- 事故先建工单(incident),恢复动作记录到工单,治理缺陷另建 governance 工单。
- 只读优先:排查阶段一律只读查询,恢复性写操作需运维授权后再执行。
- 时间一律北京时间:所有业务时间按
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 判断缺口时间窗
- 从日志找到最后一条
success的end_time(北京时间); - 缺口 = 该
end_time之后 → 当前时刻,或按业务日00:00–24:00分段; - 对照上游平台(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 事件处理复盘
- 发现:运维对比近 7 日
cs_records数据量,09-02 仅 1699 条(正常 5000–6000)。 - 定位:同步日志显示 ai-acc 大范围导出返回 HTTP 500,熔断后当天未再探活。
- 恢复:上游恢复后手动指定 09-02 单日补跑;或等次日 8:00 的 7 天兜底覆盖。
- 闭环:事故工单
#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 起,后端在真正要启动浏览器之前(点「扫码登录」那一刻)会:
- 检查
$DISPLAY指向的 X socket 是否真的存在; - 不存在且镜像里有
xvfb-run→ 自动拉起Xvfb :99并注入DISPLAY(幂等,已起则复用); - 拉不起来才返回 501,并提示用「命令行」按钮走链路 B。
因此:容器装了 xvfb 包(本仓库默认镜像已装)时,直接重试「扫码登录」即可,无需人工登服务器起进程。 确认自愈是否生效:
# 点一次「扫码登录」后执行 —— 应看到 DISPLAY 有值 且 socket 存在
ssh <服务器> "docker exec csctalon-backend sh -c 'echo DISPLAY=[\$DISPLAY]; ls /tmp/.X11-unix/'"
设计取舍(为什么不常驻 Xvfb):常驻要多一个进程长期占内存;按需拉起零常驻开销, 代价是首次点「扫码登录」多约 1 秒。后端是单 worker,极端情况下自我修复会短暂占用该 worker(上限约 5 秒)。
⚠️ 实现要点(踩过的两个坑,改代码时别退回去):
- 必须用
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不开端口),不扩大暴露面。- 判据必须"能连上 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]' " # 看进程数是否回落
八、禁忌
- 禁止在未确认缺口范围时直接全量重拉 7 天窗口(易再次触发 500 且覆盖历史)。
- 禁止用
incremental补跑历史缺口(增量只接续上次成功点,不覆盖历史断层)。 - 禁止跳过只读排查直接对生产数据做写操作。
- 禁止在工单外口头完成恢复而不留痕。