docs: p5 preparation complete and implementation draft

- 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>
This commit is contained in:
浅念
2026-09-21 01:56:56 +08:00
parent f71a887f17
commit d01cecd2f6
7 changed files with 287 additions and 34 deletions
+8 -3
View File
@@ -92,13 +92,18 @@
```bash
# 隐藏照片 → 失效该照片全部 variants 路径(对象键含 photoId 与内容版本;
# 方案 b 下才需要——variants 是 immutable 缓存,隐藏 = 主动失效 + 公开 API 404 双保险)
# 多 URL 用换行分隔(RefreshObjectCatches/PreloadObjectCaches 的 ObjectPath 约定)
aliyun cdn RefreshObjectCaches \
--ObjectPath "https://<图片域名>/variants/thumb/<photoId>/<版本>.webp;https://<图片域名>/variants/browse/<photoId>/<版本>.webp" \
--ObjectPath "$(printf '%s\n%s' \
'https://<图片域名>/variants/thumb/<photoId>/<版本>.webp' \
'https://<图片域名>/variants/browse/<photoId>/<版本>.webp')" \
--ObjectType File
# 新活动发布 → 预热首页与首屏静态资源(.viewer 域名,降低冷缓存首访时延)
# 新活动发布 → 预热首页与首屏静态资源(viewer 域名,降低冷缓存首访时延)
aliyun cdn PreloadObjectCaches \
--ObjectPath "https://<viewer域名>/;https://<viewer域名>/assets/<新版本hash>.js;https://<viewer域名>/assets/<新版本hash>.css"
--ObjectPath "$(printf '%s\n%s' \
'https://<viewer域名>/assets/<新版本hash>.js' \
'https://<viewer域名>/assets/<新版本hash>.css')"
# 目录级失效(发版后 SPA HTML 变更;范围大时用,注意每日失效配额)
aliyun cdn RefreshObjectCaches --ObjectPath "https://<viewer域名>/" --ObjectType Directory
+10 -8
View File
@@ -16,7 +16,7 @@
| 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` 日志 + `stat-snapshots` statVersion 前进检查(§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 证书管理提示 + 云监控站点监控 | 各自 |
@@ -86,7 +86,7 @@ docker logs --since 10m photo-live-cn-sync-1 2>&1 \
# ①' 同步水位延迟(未投递账本堆积——发布前快照同款 SQL,阈值见 §4)
docker exec <cn-mysql容器> mysql -uphoto -p -e \
"SELECT COUNT(*) AS undelivered, MIN(createdAt) AS oldest FROM photo_cn.outbox_events WHERE deliveredAt IS NULL;"
"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 \
@@ -98,16 +98,18 @@ docker logs --since 10m photo-live-cn-api-cn-1 2>&1 \
# ③ 计数差异(stat 回传断流 + 版本不前进)
docker logs --since 30m photo-live-cn-sync-1 2>&1 \
| jq -c 'select(.msg | contains("stat return cycle failed"))'
# 版本前进检查(statVersion 应随互动增长单调;长期不变即聚合停滞):
# 版本前进检查(statsVersion 应随互动增长单调;长期不变即聚合停滞——
# 统计副本位内联在 albums/photos 行上:viewCountReplica/likeCount + statsVersion):
docker exec <cn-mysql容器> mysql -uphoto -p -e \
"SELECT albumId, MAX(statVersion) AS v, MAX(updatedAt) AS lastAt FROM photo_cn.stat_snapshots GROUP BY albumId ORDER BY lastAt DESC LIMIT 10;"
"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: map(select(.>0)) | length}'
# 兜底 SQL:pending 行龄( oldest 超过 5 分钟即真积压)
| 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(createdAt) AS oldest FROM photo_cn.photo_jobs WHERE state='pending';"
"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 \
@@ -125,7 +127,7 @@ docker logs --since 10m photo-live-cn-api-cn-1 2>&1 \
| sync 推送失败 | 10 分钟内 `outbox push failed` ≥ 3 次(对应退避 3 轮未自愈) | 日志检索 ①(人工/计划任务) | 【占位:运维负责人】 |
| 同步水位延迟 | `outbox_events` 未投递 > 1000 行,或 oldest > 10min | SQL ①'(发布检查单同款,可入 1Panel 计划任务) | 【占位】 |
| 上传失败率 | uploads 路径非 2xx 占比 > 5%(10 分钟窗,样本 ≥ 20) | 日志检索 ② | 【占位】 |
| 计数差异 | `stat return cycle failed` 连续 30 分钟,或 stat_snapshots 最新 lastAt > 30min 前且互动非零 | 日志/SQL ③ | 【占位】 |
| 计数差异 | `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 次 | 日志检索 ⑥ / 云监控站点监控 | 【占位】 |