d01cecd2f6
- docs/plans/2026-09-21-P5-真实环境接入.md 草案(头部标注待 G5 凭证评审 转正):T1-T12 任务序(每任务 产出/依赖用户输入/关口)——G1 OSS 冒烟→ 国内起栈→ov 部署+bootstrap 对账→域名证书→CDN(含图片加速三选一)→ 跨境 MySQL 通道定案(白名单+SSL/隧道/HTTP 化三选项,含改造量评估)→ G4 用户源→故障演练→压测 D05→G2 真机→G6 试运行→发布交接;用户输入 汇总表 + 决策 D03-D13 状态速查 + 风险表(跨境链路/双层签名/MySQL 8.0.30 门槛/镜像仓库通道等) - 总纲 P5 行注记:准备件完成 2026-09-21,实装待 G5 + G4 - P5-prep 计划全项勾选回填(评审项注明自审已修 + 独立 reviewer 由编排方追加) - 自审修正:monitoring 统计副本表名(无 stat_snapshots 表,副本位在 albums/photos 行)与 outbox_events.occurredAt/photo_jobs.updatedAt 列名、 jq 管道括号;cdn 失效多 URL 换行分隔;k6 helpers 请求体用 ArrayBuffer 原生形态、read-feed 身份串延迟生成(init 上下文 VU 编号恒 0) Co-Authored-By: Claude Code <noreply@anthropic.com>
155 lines
12 KiB
Markdown
155 lines
12 KiB
Markdown
# 监控告警方案(P5-prep Task 3)
|
||
|
||
> 适用对象:photo-live 双地域生产栈(编排见 `docker-compose.cn.yml` /
|
||
> `docker-compose-ov.yml`)。**治理决策(登记):P5 起步不引入额外监控系统**
|
||
>(Prometheus/Grafana/ELK 均不上)——1Panel 应用监控 + 云监控 + 结构化日志
|
||
> 检索三件套覆盖本阶段全部指标面;业务量或团队规模变化时再评估升级,
|
||
> 届时优先补「日志聚合 + 指标导出」,而非替换现有日志结构。
|
||
|
||
## 1. 指标 → 实现方式映射总表
|
||
|
||
| 指标 | 实现方式 | 位置 |
|
||
| --- | --- | --- |
|
||
| 服务存活 / health | 容器 healthcheck(api-cn/api-ov/web 有 `/v1/health` 探针;worker/sync 无 HTTP 面,以进程存活 + restart 策略承担)+ 1Panel 容器状态页 | 两台 1Panel |
|
||
| 容器重启次数 | 1Panel 容器监控(重启计数突增 = crash loop) | 两台 1Panel |
|
||
| CPU / 内存 / 磁盘容量 | 1Panel 应用监控 + 主机概览;云监控(ECS 自带)做兜底告警 | 两台 |
|
||
| MySQL / Redis 连接与资源 | 1Panel 数据库监控(连接数、内存);连接池上限已在连接串 `connection_limit=10` 固化 | 两台 |
|
||
| 同步失败 / 同步水位延迟 | sync 容器结构化日志检索(§3 检索语句)+ `outbox_events` 未投递计数(SQL) | cn 服务器 |
|
||
| 上传失败率 | api-cn 访问日志(pino-http)按 `req.url` + `res.statusCode` 计数(§3) | cn 服务器 |
|
||
| 计数差异(点赞/浏览对不上) | sync `stat return cycle failed` 日志 + `albums`/`photos` 副本 `statsVersion` 前进检查(§3) | cn 服务器 |
|
||
| 队列积压(图片处理) | worker `sweep 完成` 日志的 `claimed` 持续 >0 + `photo_jobs` pending 行龄 SQL(§3) | cn 服务器 |
|
||
| 磁盘 / 流量(OSS) | OSS 控制台用量 + 云监控;CDN 接入后加 CDN 控制台流量看板 | 阿里云控制台 |
|
||
| 证书到期 / 域名解析 | 1Panel 证书管理提示 + 云监控站点监控 | 各自 |
|
||
|
||
要点:**业务指标不带独立监控系统,全部落在结构化日志检索**。三端(api /
|
||
worker / sync)日志均为 pino JSON 单行 stdout,`docker logs` 直接可查;字段
|
||
口径见 §2(如实列自代码,非设想)。
|
||
|
||
## 2. pino 结构化日志字段表
|
||
|
||
日志器装配(代码事实):api = `nestjs-pino` + `pino-http`(`services/api/src/
|
||
shared/logger-options.ts`,每请求自动访问日志);worker / sync = 原生 pino
|
||
(`services/worker/src/main.ts` / `services/sync/src/main.ts`)。级别映射 pino
|
||
数值:20=debug 30=info 40=warn 50=error。凭据打码:`req.headers.authorization`
|
||
与 `req.headers.cookie` 已按 `logger-options.ts` redact 为 `[REDACTED]`。
|
||
|
||
### 2.1 通用字段(每行日志必有)
|
||
|
||
| 字段 | 来源 | 示例 / 说明 |
|
||
| --- | --- | --- |
|
||
| `level` | pino | `30`(数值;人读用 `docker logs … \| jq -r '.level'` 映射) |
|
||
| `time` | pino | Unix 毫秒时间戳 |
|
||
| `pid` / `hostname` | pino | 进程 / 容器主机名(排障定位容器) |
|
||
| `msg` | 各调用点 | 人类可读消息(检索主键,见表 2.3) |
|
||
| `context` | nestjs-pino(仅 api) | Nest Logger 上下文名,如 `AllExceptionsFilter`、`UserSyncService` |
|
||
|
||
### 2.2 HTTP 访问日志字段(api 每请求一行,pino-http 自动打点)
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `req.id` | 请求 ID:透传上游 `x-request-id` 或现生成 UUID;**同时回写响应头 `x-request-id`**,全链路排障锚点(`request-id.middleware.ts`) |
|
||
| `req.method` / `req.url` | 方法与含 query 的路径(如 `GET /v1/public/albums/tok/photos?limit=30`) |
|
||
| `req.headers` | 已 redact 授权头/Cookie |
|
||
| `req.remoteAddress` / `req.remotePort` | 客户端地址(反代后是网关地址,trust proxy 登记为 P5 项) |
|
||
| `res.statusCode` | 响应状态码(错误率计数的取数字段) |
|
||
| `responseTime` | 服务端耗时毫秒(粗粒度性能观测;精测用压测框架 `loadtest/`) |
|
||
| `msg` | 固定 `request completed` |
|
||
|
||
### 2.3 业务/后台进程自定义字段(msg 为检索主键,如实列自调用点)
|
||
|
||
| 进程 | `msg`(检索用) | 级别 | 自定义字段 | 语义 |
|
||
| --- | --- | --- | --- | --- |
|
||
| sync | `outbox batch pushed` | info | `picked`(取数)`acceptedEventIds[]`(ov 确认)`applied`(实际应用) | 推送健康;`picked≥batchSize` 连续出现 = 排积压 |
|
||
| sync | `outbox push failed(整批未标记,下轮重推)` | error | `err` `failureStreak` `backoffMs` | **同步失败告警主信号**;连续出现 = cn→ov 中断 |
|
||
| sync | `reconcile cycle failed(幂等安全,下轮再试)` | error | `err` | 对账循环失败(5min 一轮自愈) |
|
||
| sync | `stat snapshots returned` | info | `picked` `snapshots` `accepted` | 统计回传健康;长期不出现 = ov 聚合停滞 |
|
||
| sync | `stat return cycle failed(未确认行留待下轮重发,幂等安全)` | error | `err` | **计数差异告警信号**(回传断流) |
|
||
| sync | `sync sender + reconciler + stat-return started` | info | `cnApiUrl` `ovApiUrl` `intervalMs` `batchSize` `reconcileIntervalMs` `statReturnIntervalMs` | 启动自描述(排障先看这行核对 env) |
|
||
| sync | `env 非正整数,回退契约默认值` / `缺少内部同步密钥,发送器退出` | warn / error | `name,raw` / `env` | 配置异常 |
|
||
| worker | `process job 完成` | info | `photoId` `jobId` `outcome` | 单照片流水线完成 |
|
||
| worker | `process job 终败(重试耗尽)` | error | `photoId` `jobId` `errorCode` | **图片处理失败告警**(errorCode 对照契约错误码) |
|
||
| worker | `sweep 完成` | info | `claimed`(本轮认领孤儿)`enqueueFailures` `staleReEnqueued` | **队列积压观测锚**:稳态应恒为 `claimed:0` |
|
||
| worker | `sweep 轮次异常` / `photo-process 入队失败(sweep 将补偿)` | error/warn | `err` / `photoId` | 补偿通道异常 |
|
||
| worker | `redis 连接异常(ioredis 自动重连)` | warn | `label` `err` | Redis 闪断(自愈,频发则查 Redis) |
|
||
| api | `ApiException <status> -> <code> (requestId=…)` | warn | 消息内嵌 code 与 requestId | 业务异常计数(含上传 complete 失败码) |
|
||
| api | `HttpException …` / `Unhandled exception …` | warn/error | 同上 + stack | 非业务异常(5xx 必查 requestId 关联访问日志行) |
|
||
|
||
## 3. 每类告警的检索语句样例(docker logs + jq,两台服务器上直接执行)
|
||
|
||
容器名约定(compose 项目名前缀):`photo-live-cn-sync-1` /
|
||
`photo-live-cn-worker-1` / `photo-live-cn-api-cn-1`(ov:`photo-live-ov-api-ov-1`)。
|
||
|
||
```bash
|
||
# ① 同步失败(告警口径:近 10 分钟出现 error 级 outbox push failed)
|
||
docker logs --since 10m photo-live-cn-sync-1 2>&1 \
|
||
| jq -c 'select(.msg | contains("outbox push failed"))'
|
||
|
||
# ①' 同步水位延迟(未投递账本堆积——发布前快照同款 SQL,阈值见 §4)
|
||
docker exec <cn-mysql容器> mysql -uphoto -p -e \
|
||
"SELECT COUNT(*) AS undelivered, MIN(occurredAt) AS oldest FROM photo_cn.outbox_events WHERE deliveredAt IS NULL;"
|
||
|
||
# ② 上传失败率(api 访问日志:uploads 路径非 2xx 占比;complete 失败码单独数)
|
||
docker logs --since 10m photo-live-cn-api-cn-1 2>&1 \
|
||
| jq -c 'select(.msg=="request completed" and (.req.url | contains("/v1/uploads")) and (.res.statusCode < 200 or .res.statusCode > 299))'
|
||
# 业务失败码(ALBUM_UPLOAD_DISABLED / UPLOAD_SESSION_CONFLICT / UPLOAD_VERIFY_FAILED 等):
|
||
docker logs --since 10m photo-live-cn-api-cn-1 2>&1 \
|
||
| jq -r 'select(.msg | startswith("ApiException")) | .msg' | sort | uniq -c | sort -rn
|
||
|
||
# ③ 计数差异(stat 回传断流 + 版本不前进)
|
||
docker logs --since 30m photo-live-cn-sync-1 2>&1 \
|
||
| jq -c 'select(.msg | contains("stat return cycle failed"))'
|
||
# 版本前进检查(statsVersion 应随互动增长单调;长期不变即聚合停滞——
|
||
# 统计副本位内联在 albums/photos 行上:viewCountReplica/likeCount + statsVersion):
|
||
docker exec <cn-mysql容器> mysql -uphoto -p -e \
|
||
"SELECT id, statsVersion, viewCountReplica, statsSyncedAt FROM photo_cn.albums ORDER BY statsSyncedAt DESC LIMIT 10;"
|
||
|
||
# ④ 队列积压(sweep claimed 持续 >0 = 孤儿持续被重新认领 = 消费不及或 worker 停)
|
||
docker logs --since 10m photo-live-cn-worker-1 2>&1 \
|
||
| jq -c 'select(.msg == "sweep 完成")' | jq -s 'map(.claimed) | {samples: length, nonzero: ([.[] | select(. > 0)] | length)}'
|
||
# 兜底 SQL:pending 行龄(photo_jobs 无 createdAt,以 updatedAt 近似行龄;
|
||
# oldest 超过 5 分钟即真积压——注意行被认领/重试会刷新 updatedAt)
|
||
docker exec <cn-mysql容器> mysql -uphoto -p -e \
|
||
"SELECT COUNT(*) AS pending, MIN(updatedAt) AS oldest, MIN(nextRetryAt) AS nextRetry FROM photo_cn.photo_jobs WHERE state='pending';"
|
||
|
||
# ⑤ 图片处理终败(重试耗尽的照片,需人工补处理)
|
||
docker logs --since 1h photo-live-cn-worker-1 2>&1 \
|
||
| jq -c 'select(.msg | contains("process job 终败"))'
|
||
|
||
# ⑥ 5xx 洪峰(api 全量非 5xx 校验;冒烟/压测期配合使用)
|
||
docker logs --since 10m photo-live-cn-api-cn-1 2>&1 \
|
||
| jq -c 'select(.msg=="request completed" and .res.statusCode >= 500)'
|
||
```
|
||
|
||
## 4. 告警项总表(阈值 = P5 起步建议值,试运行后按观测校准)
|
||
|
||
| 告警项 | 阈值建议 | 检测方式 | 接收人 |
|
||
| --- | --- | --- | --- |
|
||
| sync 推送失败 | 10 分钟内 `outbox push failed` ≥ 3 次(对应退避 3 轮未自愈) | 日志检索 ①(人工/计划任务) | 【占位:运维负责人】 |
|
||
| 同步水位延迟 | `outbox_events` 未投递 > 1000 行,或 oldest > 10min | SQL ①'(发布检查单同款,可入 1Panel 计划任务) | 【占位】 |
|
||
| 上传失败率 | uploads 路径非 2xx 占比 > 5%(10 分钟窗,样本 ≥ 20) | 日志检索 ② | 【占位】 |
|
||
| 计数差异 | `stat return cycle failed` 连续 30 分钟,或活跃相册 `statsSyncedAt` > 30min 前且互动非零 | 日志/SQL ③ | 【占位】 |
|
||
| 队列积压 | sweep `claimed` 连续 6 轮(约 3min)>0,或 photo_jobs pending oldest > 5min | 日志/SQL ④ | 【占位】 |
|
||
| 图片处理终败 | 1h 内 `process job 终败` ≥ 5 张 | 日志检索 ⑤ | 【占位】 |
|
||
| API 5xx 洪峰 | 10 分钟 5xx ≥ 10 次 | 日志检索 ⑥ / 云监控站点监控 | 【占位】 |
|
||
| 容器重启 | 1Panel 重启计数 1h 内 +3(crash loop) | 1Panel 应用监控 | 【占位】 |
|
||
| 磁盘容量 | 1Panel 磁盘 > 80% 告警 / > 90% 严重 | 1Panel 主机监控(自带阈值告警) | 【占位】 |
|
||
| ECS / 网络可用性 | ping / 站点探测失败 | 阿里云监控(站点监控免费档即可) | 【占位】 |
|
||
| OSS 流量异常 | 月度流量突增(对比基线 ×3) | OSS 控制台 + 云监控账单告警 | 【占位】 |
|
||
| 证书到期 | 剩余 < 14 天 | 1Panel 证书管理 / 云监控 | 【占位】 |
|
||
|
||
> 1Panel 计划任务可承载 SQL 类巡检(①'/③/④ 的 SQL + 失败时发通知),把
|
||
> 「人工检索」升级为「定时巡检」零新增组件;通知通道用 1Panel 的通知设置。
|
||
|
||
## 5. 日志保留策略
|
||
|
||
- **已固化**(compose 两栈统一 `x-logging`):docker json-file 轮转
|
||
`max-size=10m × max-file=3`,单容器上限约 30MB,六容器合计 ~180MB 上限,
|
||
磁盘不会被日志撑爆。轮转之外无中心化留存——重启/删除容器丢历史,属已知取舍。
|
||
- **建议归档(P5 试运行起执行)**:1Panel 计划任务每日低峰
|
||
`docker logs <容器> --since 24h > /opt/photo-live/logs/<容器>-$(date +%F).json`
|
||
后 gzip 存本地 30 天(约每天几 MB),异常事件窗口按 RUNBOOK §1 的「水位与
|
||
账本快照登记」随发布记录另存。归档目录加入 1Panel 备份计划即可,不建独立
|
||
日志系统。
|
||
- 排障现场优先用 `--since` 缩窗 + §3 jq 语句;跨容器串事件用 `req.id` /
|
||
`requestId=`(响应头同值,用户报障时让前端从 DevTools 复制即可定位)。
|