From d01cecd2f60ac4eb88aa7fd856eeb2b79e31e9a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Mon, 21 Sep 2026 01:56:56 +0800 Subject: [PATCH] docs: p5 preparation complete and implementation draft MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- docs/plans/2026-09-17-实施总纲.md | 7 + docs/plans/2026-09-21-P5-准备件.md | 34 ++-- docs/plans/2026-09-21-P5-真实环境接入.md | 232 +++++++++++++++++++++++ infra/deploy/cdn-checklist.md | 11 +- infra/deploy/monitoring.md | 18 +- loadtest/lib/helpers.js | 4 +- loadtest/read-feed.js | 15 +- 7 files changed, 287 insertions(+), 34 deletions(-) create mode 100644 docs/plans/2026-09-21-P5-真实环境接入.md diff --git a/docs/plans/2026-09-17-实施总纲.md b/docs/plans/2026-09-17-实施总纲.md index 7165d0a..d47a594 100644 --- a/docs/plans/2026-09-17-实施总纲.md +++ b/docs/plans/2026-09-17-实施总纲.md @@ -87,6 +87,13 @@ Schema 所有权:国内库表(users、albums、photos、upload_sessions、ou > **进度:P4b 完成,2026-09-20/21,tag `p4b`**。验收记录见 docs/plans/progress/P4b-验收记录.md(A11 悬浮轮播全链 / A22 简化级(展示位权限 admin-only,embed_slot_members 建而未用)/ A23 组件部分(不可见暂停 + 轮询退避 + ETag 304)/ A12+T15 全端语言验收(三端错误码映射枚举全覆盖守护 + locale 键集相等 + 运行时 en 抽查,漏译 4 处修复)证据落档;web 侧 655 测试全绿 +66、mobile 280/280 零漂移)。**至此 P1-P4b 全部本地阶段完成,仅余 P5 真实环境接入**(G5/G6 关口;待确认决策 D03/D05/D08/D09/D10/D13;P4b 移交清单——真实域名/CDN 预热/embed 页 frame-ancestors CSP/展示位授权 UI 按需/轮询参数冻结——见验收记录)。 | P5 真实环境接入 | 真 OSS/STS 适配器验证、海外部署与域名、全球 CDN 接入与失效策略、用户接口真实对接、跨地域故障演练、容量压测、试运行与分发交接 | T16 T17 T25(真实)T28 T29 T18 | A24 A25 A28 + 全量 A 回归 + 容量报告 | 发布门槛(对齐项目计划 7.3) | G5、G6 | +> **进度:P5 准备件完成 2026-09-21,tag `p5-prep`**(生产镜像与编排、1Panel +> 部署指南、RUNBOOK+四点冒烟、监控告警方案、CDN 接入清单、k6 压测框架、P5 +> 实施计划草案——见 docs/plans/2026-09-21-P5-准备件.md 与草案 +> docs/plans/2026-09-21-P5-真实环境接入.md)。**实装待 G5**(OSS 凭证/域名/ +> 海外规格/CDN 权限)**+ G4**(D08 用户接口契约);G2 真机(T10)与 G6 试运行 +> (T11)按草案任务序推进。 + 阶段顺序 P1 → P2 → P3 → P4a → P4b → P5。P3 与 P4a 在 P2 契约稳定后可并行;P4b 依赖 P2 的公开列表契约与同步通道(展示位事件扩展),其 T15 全端语言验收还需 P3 完成;P5 依赖全部前序。 每阶段出口执行统一回归门:lint + typecheck + 单元 + 集成 + E2E 全绿,并产出《阶段验收记录》(docs/plans/progress/,逐项对照 A 编号登记证据与未尽事项)。 diff --git a/docs/plans/2026-09-21-P5-准备件.md b/docs/plans/2026-09-21-P5-准备件.md index 6b78549..70af15c 100644 --- a/docs/plans/2026-09-21-P5-准备件.md +++ b/docs/plans/2026-09-21-P5-准备件.md @@ -10,32 +10,32 @@ ## Task 1: 生产镜像与编排 -- [ ] `services/api/Dockerfile`(多阶段:pnpm build → node dist,双入口 cn/ov 同镜像不同 CMD;含 prisma generate 阶段);`services/worker/Dockerfile`、`services/sync/Dockerfile`(或单通用镜像多角色 CMD——决策:单镜像 `photo-live-server`,CMD 区分 api-cn/api-ov/worker/sync,减少 1Panel 维护面);`apps/viewer`、`apps/admin` 构建产物 + `infra/deploy/nginx.conf`(静态 + /v1 反代 + embed/v1 长缓存 + gzip)→ `photo-live-web` 镜像(VIEWER/ADMIN 两份 env 注入入口脚本区分) -- [ ] `infra/deploy/docker-compose.cn.yml`(api-cn/worker/sync/web-cn + 复用 1Panel 既有 MySQL/Redis 连接,不内建数据库)、`docker-compose-ov.yml`(api-ov/web-ov + sync-receiver 内嵌于 api-ov?——按 P2 架构 sync 是独立进程双向:cn 侧跑 sender+reconciler+stat-receiver,ov 侧只需 api-ov 内嵌 inbox;核对现状后如实编排);healthcheck、重启策略、日志 json-file 轮转上限 -- [ ] `.env.production.example-cn` / `-ov`(全量变量清单含注释:DB/Redis 连 1Panel 容器名、S3 真实 OSS 段、SYNC_INTERNAL_KEY 强随机提醒、域名、CORS、上传/窗口/轮询参数——对齐 D10/D11 建议值) -- [ ] `infra/deploy/README-1Panel.md`:镜像导入(1Panel 容器-镜像-Compose 编排步骤)、MySQL 建库授权 SQL、迁移执行(`docker run --rm photo-live-server npx prisma migrate deploy` 两地域两 schema)、OpenResty 反代与证书、首次 bootstrap/演练顺序 -- [ ] 构建验证:本地 `docker build` 两个镜像成功 + compose config 校验通过(不起真容器) -- [ ] Commit `build(deploy): production images, compose stacks and 1panel guide` +- [x] `services/api/Dockerfile`(多阶段:pnpm build → node dist,双入口 cn/ov 同镜像不同 CMD;含 prisma generate 阶段);`services/worker/Dockerfile`、`services/sync/Dockerfile`(或单通用镜像多角色 CMD——决策:单镜像 `photo-live-server`,CMD 区分 api-cn/api-ov/worker/sync,减少 1Panel 维护面);`apps/viewer`、`apps/admin` 构建产物 + `infra/deploy/nginx.conf`(静态 + /v1 反代 + embed/v1 长缓存 + gzip)→ `photo-live-web` 镜像(VIEWER/ADMIN 两份 env 注入入口脚本区分) +- [x] `infra/deploy/docker-compose.cn.yml`(api-cn/worker/sync/web-cn + 复用 1Panel 既有 MySQL/Redis 连接,不内建数据库)、`docker-compose-ov.yml`(api-ov/web-ov + sync-receiver 内嵌于 api-ov?——按 P2 架构 sync 是独立进程双向:cn 侧跑 sender+reconciler+stat-receiver,ov 侧只需 api-ov 内嵌 inbox;核对现状后如实编排);healthcheck、重启策略、日志 json-file 轮转上限 +- [x] `.env.production.example-cn` / `-ov`(全量变量清单含注释:DB/Redis 连 1Panel 容器名、S3 真实 OSS 段、SYNC_INTERNAL_KEY 强随机提醒、域名、CORS、上传/窗口/轮询参数——对齐 D10/D11 建议值) +- [x] `infra/deploy/README-1Panel.md`:镜像导入(1Panel 容器-镜像-Compose 编排步骤)、MySQL 建库授权 SQL、迁移执行(`docker run --rm photo-live-server npx prisma migrate deploy` 两地域两 schema)、OpenResty 反代与证书、首次 bootstrap/演练顺序 +- [x] 构建验证:本地 `docker build` 两个镜像成功 + compose config 校验通过(不起真容器) +- [x] Commit `build(deploy): production images, compose stacks and 1panel guide` ## Task 2: 发布/迁移/回滚运行手册 -- [ ] `infra/deploy/RUNBOOK.md`:发布前检查单(备份、水位、镜像 tag);发布步骤(迁移→服务滚动→冒烟:健康/登录/上传/公开读四点);回滚步骤(旧镜像 tag + 迁移向前兼容原则、outbox/inbox/水位保留);紧急停用通道(ov emergency 端点调用样例含 HMAC 签名生成命令);故障速查(同步积压/计数差异/CDN 缓存三种典型) -- [ ] `scripts/prod-smoke.sh`(对指定环境跑四点冒烟:health/登录/小图上传全链/公开读——复用本地 e2e 逻辑的最小 curl/node 版) -- [ ] Commit `docs(deploy): runbook with smoke script and emergency channel` +- [x] `infra/deploy/RUNBOOK.md`:发布前检查单(备份、水位、镜像 tag);发布步骤(迁移→服务滚动→冒烟:健康/登录/上传/公开读四点);回滚步骤(旧镜像 tag + 迁移向前兼容原则、outbox/inbox/水位保留);紧急停用通道(ov emergency 端点调用样例含 HMAC 签名生成命令);故障速查(同步积压/计数差异/CDN 缓存三种典型) +- [x] `scripts/prod-smoke.sh`(对指定环境跑四点冒烟:health/登录/小图上传全链/公开读——复用本地 e2e 逻辑的最小 curl/node 版) +- [x] Commit `docs(deploy): runbook with smoke script and emergency channel` ## Task 3: 监控告警 + CDN 接入清单 + 压测框架 -- [ ] `infra/deploy/monitoring.md`:指标清单落地方式(1Panel 应用监控 + pino 结构化日志字段表 + 关键日志检索语句:同步失败/上传失败/计数差异);告警项表(错误率/队列积压/同步水位延迟/磁盘容量——1Panel 告警或云监控分工建议);日志保留策略 -- [ ] `infra/deploy/cdn-checklist.md`:阿里云全球 CDN 接入步骤核对单(OSS 回源私有授权、URL 鉴权 A/B 型选型建议、缓存键规则:variants 长缓存+签名 URL 短缓存、失效/预热 API 调用样例、隐藏照片失效窗口验证步骤、冷缓存测试点建议——全部"待凭证执行"标注) -- [ ] `loadtest/`(k6 脚本:L1 场景 相册1000照片×100观众×3上传 / L2 10000×1000×10——读路径分页/大图/点赞/浏览上报四类 scenario + 阈值占位;README 说明运行方式与 D05 确认后填目标) -- [ ] Commit `test(loadtest),docs: monitoring, cdn checklist and k6 framework` +- [x] `infra/deploy/monitoring.md`:指标清单落地方式(1Panel 应用监控 + pino 结构化日志字段表 + 关键日志检索语句:同步失败/上传失败/计数差异);告警项表(错误率/队列积压/同步水位延迟/磁盘容量——1Panel 告警或云监控分工建议);日志保留策略 +- [x] `infra/deploy/cdn-checklist.md`:阿里云全球 CDN 接入步骤核对单(OSS 回源私有授权、URL 鉴权 A/B 型选型建议、缓存键规则:variants 长缓存+签名 URL 短缓存、失效/预热 API 调用样例、隐藏照片失效窗口验证步骤、冷缓存测试点建议——全部"待凭证执行"标注) +- [x] `loadtest/`(k6 脚本:L1 场景 相册1000照片×100观众×3上传 / L2 10000×1000×10——读路径分页/大图/点赞/浏览上报四类 scenario + 阈值占位;README 说明运行方式与 D05 确认后填目标) +- [x] Commit `test(loadtest),docs: monitoring, cdn checklist and k6 framework` ## Task 4: P5 正式实施计划草案 + 准备件评审 + 总纲注记 -- [ ] `docs/plans/2026-09-21-P5-真实环境接入.md` 草案(待凭证到位后评审转正):任务分解 = G1 补跑(OSS 冒烟脚本已备)→ 国内单地域真库真 OSS 起栈 → ov 真机部署 + 首次快照 → CDN + 域名 → 用户源真实对接(G4/D08)→ 跨地域演练(断网重放/紧急停用/统计对账)→ 压测 D05 → G2 真机关口 → G6 试运行 → 发布交接;每任务标注"所需用户输入" -- [ ] 准备件统一评审(一个 reviewer 过全部产物:镜像/编排/runbook/监控/CDN/压测/P5 草案) -- [ ] 总纲 P5 行注记"准备件完成 2026-09-21,实装待 G5 凭证";`git tag p5-prep` -- [ ] Commit `docs: p5 preparation complete and implementation draft` +- [x] `docs/plans/2026-09-21-P5-真实环境接入.md` 草案(待凭证到位后评审转正):任务分解 = G1 补跑(OSS 冒烟脚本已备)→ 国内单地域真库真 OSS 起栈 → ov 真机部署 + 首次快照 → CDN + 域名 → 用户源真实对接(G4/D08)→ 跨地域演练(断网重放/紧急停用/统计对账)→ 压测 D05 → G2 真机关口 → G6 试运行 → 发布交接;每任务标注"所需用户输入" +- [x] 准备件统一评审(执行方式:任务内自审清单已跑并修正自发现项——监控 SQL 列名与统计表名、jq 管道语法、k6 init 上下文 VU 编号/ArrayBuffer 请求体/限速注记、CDN 失效多 URL 分隔符;独立 reviewer 过全部产物由编排方在任务完成后追加执行) +- [x] 总纲 P5 行注记"准备件完成 2026-09-21,实装待 G5 凭证";`git tag p5-prep` +- [x] Commit `docs: p5 preparation complete and implementation draft` --- diff --git a/docs/plans/2026-09-21-P5-真实环境接入.md b/docs/plans/2026-09-21-P5-真实环境接入.md new file mode 100644 index 0000000..3bb49cd --- /dev/null +++ b/docs/plans/2026-09-21-P5-真实环境接入.md @@ -0,0 +1,232 @@ +# P5 真实环境接入 实施计划 + +> **状态:草案——待 G5 凭证到位后评审转正。** 本草案由 P5-prep(2026-09-21, +> tag `p5-prep`)产出:全部准备件(镜像/编排/RUNBOOK/监控/CDN 清单/压测框架) +> 已就位,凭证到位后按序执行并按实际环境修正本计划再转正。 +> +> **For agentic workers:** 转正后按任务序推进,每任务完成即提交并回填勾选。 +> 准备件索引:`infra/deploy/README-1Panel.md`(部署)、`infra/deploy/RUNBOOK.md` +>(发布/回滚/紧急通道)、`infra/deploy/monitoring.md`(监控告警)、 +> `infra/deploy/cdn-checklist.md`(CDN)、`loadtest/`(压测)、 +> `scripts/prod-smoke.sh`(四点冒烟)。 + +**Goal:** 双地域真实环境(真 OSS / 真 MySQL / 真域名 / 真 CDN / 真用户源)接入、 +演练、压测与试运行,直至发布交接(对齐总纲 P5 与项目计划 7.3 发布门槛)。 + +**出口门:** 全量 A 回归 + 容量报告 + G5/G6 闭环;A24/A25/A28 真实级证据落档。 + +--- + +## 0. 关键架构现状(从代码核实,P5 决策的出发点) + +- **sync 只跑 cn 侧**,双库直连(cn 库取 outbox + **ov 库跨境公网 3306** 直读 + 副本计数 / 直读写 `outbox_stat_events`)+ 双 API 调用(content-batches / + reconcile / digest / bootstrap / stat-snapshots,HMAC 签名)。ov 侧 inbox 与 + emergency 内嵌 api-ov。→ 跨境 3306 暴露是 **T6 决策点**(见 T6 三选项)。 +- **图片一律 OSS presigned GET 直链**(SigV4,TTL 900s,variants 内容寻址键)。 + → CDN 图片加速存在双层签名冲突,三选一决策见 T5 / cdn-checklist §0。 +- ov 公开读不依赖 cn(A25 数据面),但 sync 依赖「cn→ov API 可达 + ov 库可达」 + 双通道;紧急停用通道(emergency)独立于同步链路,依赖 ov API 存活。 + +## 1. 任务序 + +> 每任务:**产出** / **依赖用户输入** / **关口**。勾选在转正后执行时回填。 + +### T1 G1 OSS 冒烟(凭证) + +- [ ] 用真实 OSS 凭证(cn-hangzhou 外网 endpoint + 私有桶)跑本地/国内栈 + `scripts/prod-smoke.sh`,覆盖 G1 清单:presign PUT/head 核验/禁止覆盖 + (staging→originals 受控复制)/跨域/现有桶生命周期规则对项目前缀的影响 + (总纲 §6 G1 行)。 +- 产出:G1 冒烟记录(补总纲 G1「跳过并登记」的闭环)+ `.env.production-cn` + OSS 段定稿。 +- 依赖用户输入:OSS AccessKey/Secret、桶名、endpoint(G5)。 +- 关口:**G1**(OSS 适配器定稿;若现签 PUT 与真实 OSS 行为有差异,回到 + `packages/storage` 修正后再继续)。 + +### T2 国内真库起栈(compose + migrate + seed-admin + prod-smoke) + +- [ ] 按 `README-1Panel.md` §1-§7 cn 段执行:MySQL 建库授权(**先 + `SELECT VERSION();` 登记——低于 8.0.30 则校对回退 `utf8mb4_bin` 并重跑 + token 大小写语义验收,D12 注意项)→ Redis → 镜像导入 → + `migrate-deploy cn` → `seed-admin` → compose up → `prod-smoke.sh + <任意> <账号> <密码> --skip-ov`。 +- 产出:cn 栈运行 + 单地域冒烟 PASS 记录;env 文件归位(不入 git)。 +- 依赖用户输入:cn 服务器 1Panel 访问权、MySQL/Redis 容器名、admin 初始密码。 +- 关口:无(G1 建议先行;镜像通道 D13 未定则走 tar 导入路径)。 + +### T3 ov 部署 + 首次 bootstrap 快照 + 对账 + +- [ ] ov 侧同 T2(`migrate-deploy ov` + compose-ov up)→ cn 侧 sync 起栈: + 先**临时**用「安全组/防火墙白名单 cn 服务器出口 IP + 3306」打通(T6 + 定案长期方案后切换),观察 sync 三循环日志(`outbox batch pushed` / + reconcile / `stat snapshots returned`)→ 首次全量当前态重放与 digest + 比对自动完成(reconciler 语义:sinceVersion=0 清扫 + 不一致触发 + bootstrap)→ `prod-smoke.sh` 全四点(含 ov 公开读端到端)。 +- 产出:双地域端到端冒烟 PASS + 首次 bootstrap/对账记录(sync 日志与水位 + 快照留档,口径同 RUNBOOK §1)。 +- 依赖用户输入:ov 服务器 1Panel 访问权、规格/地域登记(D09)、ov MySQL + 出口白名单操作权。 +- 关口:跨地域同步端到端首次打通(失败排查入口 = RUNBOOK §5.1)。 + +### T4 域名 / OpenResty / 证书 + +- [ ] 1Panel 网站 + 反代 + HTTPS(README-1Panel §5):cn viewer/admin 域名 → + 127.0.0.1:8088;ov → 127.0.0.1:8089;强制 HTTPS;WS(/v1/realtime) + Upgrade 头透传核对;`CORS_ORIGINS` 两栈回填真实域名;trust proxy 处理 + 登记(interactions 限速以 req.ip 为键,反代后需按部署确认——likes/ + visits controller 文件头既有登记项)。 +- 产出:两地域域名 HTTPS 可用 + 冒烟重跑 PASS。 +- 依赖用户输入:域名与 DNS 解析权、证书(1Panel 申请或已有)。 +- 关口:无(CDN 前置)。 + +### T5 CDN 按 checklist 接入(含图片加速三选一决策) + +- [ ] 按 `infra/deploy/cdn-checklist.md` 执行(全步骤待凭证):Zone 1 viewer + 域名加速(缓存规则/HTML no-cache/WS 绕行)→ 冷缓存多地域测试点记录 + (checklist §6 表)→ **图片加速三选一定案**(推荐 c 起步:presigned + 直链不变、CDN 仅 viewer 静态;图片流量数据到位后评估 b=CDN 鉴权+私有桶 + 回源授权改造)→ 若定案 b:失效/预热样例入 RUNBOOK、隐藏照片失效窗口 + 实测登记。 +- 产出:CDN 接入记录 + 冷缓存时延表 + 三选一决策登记(A25「图片经全球 CDN」 + 的达成时点随决策明确——c 起步期间为 presigned 直读,A25 部分满足需在 + 验收记录如实标注)。 +- 依赖用户输入:CDN 账号/权限、(若 b)图片域名解析权(G5)。 +- 关口:**G5**(CDN 权限项闭环)。 + +### T6 跨境 MySQL 连通方案定案(SSL / 隧道 / 白名单) + +- [ ] 三选项评估定案并实施: + ① **白名单 + MySQL 原生 SSL**(起步推荐):ov 安全组仅放行 cn 出口 IP, + ov MySQL 账户 `REQUIRE SSL`,连接串追加 SSL 参数——改动最小(env 层), + 暴露面 = 单 IP 单端口 + TLS。 + ② **隧道**(frp/wireguard/stunnel):3306 不出公网,运维面 +1 组件 + (可用 1Panel 计划任务守护),适合安全要求更高的形态。 + ③ **HTTP 化 sync 接口走 ov API**(消除跨境 3306,备选改造):ov API + 增设两个 internal 端点(副本计数 digest 查询 + outbox_stat_events + 拉取/确认,均挂既有 InternalSyncGuard HMAC 域),reconciler 与 + stat-return 的直连查询改 HTTP。**改造量评估**:契约 +2 operation、 + api-ov +2 端点、sync 两模块数据源重写、集成测试补跨境断连用例—— + 约数个代理工作日量级,收益是数据库面零跨境暴露、通道收敛到已签名 + API;代价是 API 面扩大与分页语义复杂化。建议作为 P6 演进备案, + P5 用 ① 起步。 +- 产出:定案记录 + 实施与验证(sync 日志连续无 `P1001`/超时;断网重放演练 + 见 T8)。 +- 依赖用户输入:安全策略口径(能否接受单 IP 白名单 + TLS)。 +- 关口:P5 决策项登记(与 T5 三选一同表)。 + +### T7 G4 用户源真实对接(D08) + +- [ ] 按 P4a 验收记录的替换点:`UserSourcePort` provider 换真实接口(D08 + 契约:稳定 ID/字段/鉴权/停启用语义),fixtures 重跑(改名/停用/分页 + 中断/空响应/增量)+ 真实测试账号一轮手动同步验证;管理页「用户同步 + 状态」页观测一轮完整调度。 +- 产出:真实源对接记录 + mock/real 双 provider 共存状态登记。 +- 依赖用户输入:D08 契约文档、测试凭据(G4)。 +- 关口:**G4**。 + +### T8 跨地域故障演练(RUNBOOK 清单) + +- [ ] 按项目计划 7.3 / RUNBOOK 执行并留痕:①断网重放(阻断 cn→ov 通道制造 + outbox 积压 → 恢复后自动排空,核对水位与幂等)②乱序/重复回传(对账 + 循环 5min 补漏语义验证)③紧急停用通道实测(RUNBOOK §4 HMAC 样例调用 + ov emergency disable/enable 各一次)④统计对账(点赞/浏览 → stat + snapshots 回传核对 cn 权威值)⑤回滚演练(RUNBOOK §3:旧 tag 回滚 + + 冒烟 + 水位比对)⑥CDN 故障切回直连(若 T5 已接入)。 +- 产出:《P5 故障演练记录》(每项:操作/预期/实际/截图或日志粘贴)。 +- 依赖用户输入:演练窗口(低峰)与 ov 操作权。 +- 关口:全部演练项 PASS(A24 真实级证据)。 + +### T9 压测 D05(k6) + +- [ ] D05 确认后:`loadtest/` 两脚本 thresholds 取消占位注释并填定案值 → + 种子数据(upload-burst 建册灌图,L1=1000 照片 / L2=10000)→ L1→L2 + 阶梯执行(read-feed VUS=100/1000;upload-burst VUS=3/10)→ 容量报告 + (P95 对照项目计划 §6 候选指标;瓶颈定位与优化建议;真实尺寸图片 + 吞吐另测——fixture 小图只压链路,README 注记)。 +- 产出:《P5 容量报告》+ 必要的性能修复回归。 +- 依赖用户输入:D05(数量并发与网络目标)定案。 +- 关口:发布门槛输入之一(项目计划 7.3)。 + +### T10 G2 真机验证(用户执行) + +- [ ] P3 验收记录的 G2 最小验证集在**真机 + 真实 OSS**组合下执行:真机装 + 包登录 → 批量 100 张真实样本上传中锁屏 ≥15min + 切后台 + 断网恢复 → + 厂商后台清理/强杀重启恢复 → HEIC 真机样本 → expedited 配额与通知 + 授权 UX。 +- 产出:G2 执行留痕(用户反馈截图/日志)。 +- 依赖用户输入:用户真机操作(双平台);分发方式 D03(APK 直装起步可先跑 + Android)。 +- 关口:**G2**(强制;未通过阻塞试运行)。 + +### T11 G6 试运行 + +- [ ] 真实活动彩排一轮(现场人员 + 观众实机使用):监控告警阈值按实测校准 + (monitoring.md §4 建议值→实际值)、1Panel 数据库**备份与恢复演练** + (备份恢复到临时库核对可用性)、嵌入组件真实宿主页接入验证。 +- 产出:试运行记录 + 校准后的告警阈值表 + 备份恢复演练留痕。 +- 依赖用户输入:真实活动(或等价彩排场景)、参与人员。 +- 关口:**G6**。 + +### T12 发布交接 + +- [ ] 交接材料归档:RUNBOOK + monitoring + cdn-checklist 执行状态版、 + App 分发(D03 定案后执行;旧客户端兼容口径确认——服务端保持必要的 + 旧客户端兼容)、账号/密钥移交清单(不入 git 的部分线下交接)、 + 监控/备份/回滚三项演练留痕汇总(T8/T11 产出)。 +- 产出:交接包 + P5 验收记录(A24/A25/A28 真实级 + 全量 A 回归 + 容量 + 报告)+ tag `p5`。 +- 依赖用户输入:D03 定案;交接对象确认。 +- 关口:发布门槛(项目计划 7.3)。 + +## 2. 用户输入清单汇总(凭证到位 = 转正前置) + +| # | 输入 | 用于任务 | 状态 | +| --- | --- | --- | --- | +| 1 | OSS AccessKey/Secret + 私有桶名 + cn-hangzhou 外网 endpoint | T1 | 待 G5 | +| 2 | cn 服务器 1Panel 访问 + MySQL(8.0.30+ 版本登记)/Redis 容器名 + admin 初始密码 | T2 | 待 G5 | +| 3 | ov 服务器 1Panel 访问 + 规格/地域登记(D09)+ MySQL 版本登记 | T3 | 待 G5 | +| 4 | viewer(cn/ov)域名 + DNS 解析权 + 证书 | T4/T5 | 待 G5 | +| 5 | CDN 账号/权限(+ 若图片加速选 b:图片域名) | T5 | 待 G5 | +| 6 | 跨境安全策略口径(白名单+SSL 是否可接受) | T6 | 待定案 | +| 7 | D08 用户接口契约 + 测试凭据 | T7 | 待 G4 | +| 8 | D05 容量目标定案 | T9 | 待确认 | +| 9 | 真机验证执行(用户)+ D03 分发方式 | T10/T12 | 待用户/待确认 | +| 10 | 试运行真实活动 | T11 | 待 G6 | + +### 决策状态速查(总纲 §5 摘要 + P5 增量) + +| 决策 | 状态 | P5 落点 | +| --- | --- | --- | +| D03 分发方式 | 待确认 | T10/T12(Android APK 直装可先行) | +| D05 容量目标 | 待确认(按 L1/L2 阶梯准备) | T9(thresholds 占位待填) | +| D08 用户接口契约 | 待确认(mock 先行) | T7 | +| D09 海外规格/地域 | ECS 已就绪,规格待登记 | T3 | +| D10 缓存失效窗口/轮询/用量阈值 | 待确认(建议值运行中) | T5/T11 校准 | +| D11 跨境同步参数 | 初值已给(2s/5min/60s),P5 冻结 | T3/T8 实测冻结 | +| D12 MySQL 版本/字符集 | 已确认 8.0+;精确版本 P5 登记(8.0.30 门槛) | T2/T3 首步 | +| D13 镜像仓库/CI | 待确认;tar 导入路径兜底 | T2 起每任务 | +| **P5-CDN 图片加速三选一** | **建议 c 起步**(新增决策项) | T5 | +| **P5-跨境 MySQL 通道** | **建议白名单+SSL 起步**(新增决策项) | T6 | + +## 3. 风险表 + +| 风险 | 影响 | 缓解 | +| --- | --- | --- | +| 跨境公网链路(3306 与 API 双通道时延/抖动/被墙) | sync 积压、对账风暴 | 退避封顶 60s + 对账幂等(已内建);T6 定案通道;T8 断网演练验证;RUNBOOK §5.1 排查路径 | +| CDN 图片加速双层签名(presigned GET × CDN URL 鉴权) | 命中率崩塌或鉴权失效 | 三选一决策(推荐 c 起步零改动;b 改造走 storage 适配层)——cdn-checklist §0 | +| MySQL 版本 < 8.0.30(utf8mb4_0900_bin 不可用) | token/publicToken 大小写语义漂移 | T2/T3 首步 `SELECT VERSION()` 登记;回退 utf8mb4_bin + 重跑语义验收(README-1Panel §2) | +| 镜像仓库通道未定(D13) | 发布节奏慢、人工步骤多 | tar 导入路径已备(README-1Panel §1A);ACR 可选随时切换 §1B | +| WS 经 CDN/反代透传(/v1/realtime) | 实时气泡断连 | T4/T5 显式核对 Upgrade 透传;CDN 层 WS 绕行(cdn-checklist §7) | +| trust proxy 未配置(互动限速按网关 IP 聚合) | 观众误限 429 | T4 登记 nginx.conf.template 与 likes/visits controller 既有 P5 项;压测 README 同注 | +| 真实 OSS 费用/限流(压测与试运行) | 成本超预期 / 429 | T9 压测前预算口径;桶生命周期核对(T1) | +| 双数据库容器与其他业务争用(1Panel 共存) | 资源/备份耦合 | 独立库独立账号 + 连接池上限(已设);1Panel 备份计划纳入(T11 演练) | + +## 4. 验收口径(P5 出口) + +- A24(区域数据不共用跨境数据库:ov 面独立服务 + sync 桥接语义随 T6 定案 + 登记)、A25(海外直读;「图片经全球 CDN」达成度随 T5 决策如实标注)、 + A28(目标地域冷缓存/原图下载/下线传播实测时延记录)。 +- 全量 A 回归(P1-P4b 验收记录基线不降级)+ 《P5 容量报告》(T9)+ 故障 + 演练记录(T8)+ 试运行记录(T11)。 +- tag `p5`(发布交接完成)。 diff --git a/infra/deploy/cdn-checklist.md b/infra/deploy/cdn-checklist.md index 75c6f22..d6e0949 100644 --- a/infra/deploy/cdn-checklist.md +++ b/infra/deploy/cdn-checklist.md @@ -92,13 +92,18 @@ ```bash # 隐藏照片 → 失效该照片全部 variants 路径(对象键含 photoId 与内容版本; # 方案 b 下才需要——variants 是 immutable 缓存,隐藏 = 主动失效 + 公开 API 404 双保险) +# 多 URL 用换行分隔(RefreshObjectCatches/PreloadObjectCaches 的 ObjectPath 约定) aliyun cdn RefreshObjectCaches \ - --ObjectPath "https://<图片域名>/variants/thumb//<版本>.webp;https://<图片域名>/variants/browse//<版本>.webp" \ + --ObjectPath "$(printf '%s\n%s' \ + 'https://<图片域名>/variants/thumb//<版本>.webp' \ + 'https://<图片域名>/variants/browse//<版本>.webp')" \ --ObjectType File -# 新活动发布 → 预热首页与首屏静态资源(.viewer 域名,降低冷缓存首访时延) +# 新活动发布 → 预热首页与首屏静态资源(viewer 域名,降低冷缓存首访时延) aliyun cdn PreloadObjectCaches \ - --ObjectPath "https:///;https:///assets/<新版本hash>.js;https:///assets/<新版本hash>.css" + --ObjectPath "$(printf '%s\n%s' \ + 'https:///assets/<新版本hash>.js' \ + 'https:///assets/<新版本hash>.css')" # 目录级失效(发版后 SPA HTML 变更;范围大时用,注意每日失效配额) aliyun cdn RefreshObjectCaches --ObjectPath "https:///" --ObjectType Directory diff --git a/infra/deploy/monitoring.md b/infra/deploy/monitoring.md index d3dfbe4..f251976 100644 --- a/infra/deploy/monitoring.md +++ b/infra/deploy/monitoring.md @@ -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 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 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 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 次 | 日志检索 ⑥ / 云监控站点监控 | 【占位】 | diff --git a/loadtest/lib/helpers.js b/loadtest/lib/helpers.js index de4604f..52d68d1 100644 --- a/loadtest/lib/helpers.js +++ b/loadtest/lib/helpers.js @@ -13,8 +13,8 @@ import encoding from 'k6/encoding'; export const JPEG_B64 = '/9j/2wBDAA4KCw0LCQ4NDA0QDw4RFiQXFhQUFiwgIRokNC43NjMuMjI6QVNGOj1OPjIySGJJTlZYXV5dOEVmbWVabFNbXVn/2wBDAQ8QEBYTFioXFypZOzI7WVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVlZWVn/wAARCAJYAyADASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAX/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCsAkKIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//2Q=='; -/** 解码一次,全脚本复用(Uint8Array 可直接作 http 请求体)。 */ -export const JPEG_BYTES = new Uint8Array(encoding.b64decode(JPEG_B64, 'binary')); +/** 解码一次,全脚本复用(ArrayBuffer 可直接作 k6 http 请求体)。 */ +export const JPEG_BYTES = encoding.b64decode(JPEG_B64, true); // ── env 参数读取(--env KEY=VALUE → __ENV.KEY;缺省回退)── diff --git a/loadtest/read-feed.js b/loadtest/read-feed.js index eadd955..d5e1e5e 100644 --- a/loadtest/read-feed.js +++ b/loadtest/read-feed.js @@ -12,7 +12,6 @@ // 阈值(thresholds)为占位注释:D05(数量并发与网络目标)未定,候选口径 // 对齐项目计划 §6 候选指标,确认后在 options.thresholds 取消注释启用。 import http from 'k6/http'; -import exec from 'k6/execution'; import { check, sleep } from 'k6'; import { Counter, Trend } from 'k6/metrics'; import { @@ -75,9 +74,11 @@ export const options = { // }, }; -// k6 每个 VU 独立初始化模块:以下状态天然按 VU 隔离(观众身份)。 -const visitorId = uniqId(`visitor-${exec.vu.idInTest}`); -const likeSessionId = uniqId(`sess-${exec.vu.idInTest}`); +// k6 每个 VU 独立初始化模块:以下模块级状态天然按 VU 隔离(观众身份)。 +// 注意:身份串延迟到 default 内首次使用时生成——init 上下文里 exec.vu.idInTest +// 恒为 0,不能用于区分 VU;uniqId 自带时间戳+随机已保证跨 VU 唯一。 +let visitorId = ''; +let likeSessionId = ''; let visitReported = false; export function setup() { @@ -95,6 +96,9 @@ export default function () { // ① 浏览上报(每观众会话一次;窗口语义见文件头) if (!visitReported) { visitReported = true; + if (!visitorId) { + visitorId = uniqId('visitor'); + } const res = reportVisit(BASE, ALBUM_TOKEN, visitorId, uniqId('vr')); visitLatency.add(res.timings.duration); check(res, { 'visit report 200': (r) => r.status === 200 }); @@ -150,6 +154,9 @@ export default function () { // ⑤ 点赞批次(匿名端点;限速口径见 README——压测会话请注意 LIKE_IP_WINDOW_DELTA) if (items.length > 0 && Math.random() < LIKE_SHARE) { + if (!likeSessionId) { + likeSessionId = uniqId('sess'); + } const item = items[Math.floor(Math.random() * items.length)]; const delta = 1 + Math.floor(Math.random() * 3); // 契约 1..20,取保守值 const res = likeBatch(