Files
FruitServer/docs/backend-design-phase1.md
Vicky b3179fc836 refactor: 后端标识统一为 FruitShorts,并落地 05 离线下载
为什么:
1) 产品已定名 FruitShorts,但后端仍是 reelshort 系列标识 —— Java 包 com.reelshort、
   模块名与 jar 名 reelshort-*、spring.application.name、配置前缀 reelshort.*,
   这些会出现在启动日志、命令行和 IDE 里,与产品名不一致。本次整体替换为 fruitshorts,
   数据库连接名上一轮已改名,保持不变。
2) 第一阶段下一批第 1 步(05 离线下载)要求把下载授权(设备绑定/有效期/吊销)、
   下载任务与断点续传、配额与过期清理、仅 Wi-Fi 规则做成完整接口,本提交一并落地。
3) 顺带修复:schema-phase1.sql 里 playback_ticket / order_event 仍按普通表定义唯一索引,
   表分区化之后重跑脚本必定报错(分区表的唯一索引必须包含分区键)。改为在本脚本里就
   按分区表定义,并验证脚本可重复执行。
2026-10-15 11:44:26 +08:00

293 lines
17 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 后端服务设计(第一阶段 · 印尼市场 · 1 万并发观看)
> 版本 v1.0 · 配套 [第一阶段 PRD](./prd-phase1.md) · 现有实现见 [架构设计](./architecture-design.md)
> 目标口径:**10,000 路并发播放不劣化**(对应 DAU 约 25–35 万),首发印尼。
## 1. 目标与约束
**业务目标**:日活规模下 1 万路并发观看流畅(首帧 P95 < 1.5s、卡顿率 < 2%),分集收费闭环准确无误,创作者上传到可播放全自动。
**工程约束**:
1. 首发印尼,用户全部在东南亚,服务端与内容必须就近部署,不能用国内机房直连。
2. 团队是 14–16 人的 MVP 团队,运维只有 1 人,所以不做微服务拆分,保持模块化单体。
3. 数字商品必须走 Apple IAP / Google Play Billing,服务端只做校验与权益发放。
4. 成本敏感:视频流量是最大成本项,码率与缓存策略从一开始就要按成本设计。
## 2. 现状盘点与差距
当前实现:7 个 Maven 模块的模块化单体,`fruitshorts-admin-api`(8080) + `fruitshorts-app-api`(8081),
MyBatis-Plus + PostgreSQL,媒资落本地磁盘 `data/uploads`,静态文件直出,无转码、无 CDN、无缓存层、无消息队列。
| 能力 | 现状 | 目标 | 优先级 |
| --- | --- | --- | --- |
| 媒资存储 | 本地磁盘,随机器走 | 对象存储(S3 兼容)+ 生命周期分层 | P0 |
| 视频分发 | 源站直出原文件 | 转码成 HLS 多码率 + CDN 分发 + 签名 URL | P0 |
| 缓存 | 无 | Redis + 本地 Caffeine 二级缓存 | P0 |
| 异步 | 无 | 消息队列(转码任务、计数、埋点、对账) | P0 |
| 数据库 | 单实例单库 | 主库 + 只读副本,热点与日志表分区 | P1 |
| 播放鉴权 | 免费集直接给地址 | 票据 + 签名 URL + 有效期 + 吊销 | P0 |
| 限流熔断 | 无 | 网关级限流 + 按接口降级 | P0 |
| 支付 | 未实现 | 订单状态机 + 票据校验 + 对账 | P0 |
| 可观测 | 慢请求日志 | 指标 + 链路追踪 + 告警 + 看板 | P1 |
结论:现有代码是功能验证级,架构骨架(模块划分、鉴权、内容与媒资模型)可以直接复用,
但"视频从源站直出"这一点在 1 万并发下会立刻打爆,必须替换成 CDN + 签名 URL。
## 3. 容量规划
### 3.1 码率阶梯与带宽
印尼以 4G 为主、资费敏感,码率阶梯压到偏低档位:
| 档位 | 分辨率 | 视频 | 音频 | 合计 | 预期占比 |
| --- | --- | --- | --- | --- | --- |
| 360p | 640x360 | 400 kbps | 64 kbps | ~0.47 Mbps | 40% |
| 480p | 854x480 | 800 kbps | 64 kbps | ~0.87 Mbps | 45% |
| 720p | 1280x720 | 1600 kbps | 96 kbps | ~1.70 Mbps | 12% |
| 1080p | 1920x1080 | 3200 kbps | 128 kbps | ~3.30 Mbps | 3% |
按占比加权,平均码率约 **0.88 Mbps**。
- 10,000 路并发出口带宽约 **8.8 Gbps**
- 叠加首段突发、重连与预加载(+30%),峰值 **约 11–12 Gbps**
- CDN 命中率按 98% 计,回源带宽 **约 240 Mbps**
源站只需 2 台带 1 Gbps 出口的机器即可承载回源,不需要为每个观众准备源站带宽。
这条推导是整个设计的关键:**播放流量必须由 CDN 承担,源站只回源 2%**。
### 3.2 接口 QPS
| 链路 | 估算方式 | 平均 | 峰值(x3,20:00–23:00 WIB) |
| --- | --- | --- | --- |
| 首页/发现/详情 | 每活跃用户每分钟 2 次 | ~333 QPS | ~1,000 QPS |
| 播放票据签发 | 每集一次,平均 4 分钟/集 | ~42 QPS | ~125 QPS |
| 播放进度上报 | 每 10 秒一次 | 1,000 QPS | 3,000 QPS |
| 埋点/播放计数 | 客户端按 30s 合批 | ~330 QPS | ~1,000 QPS |
结论:
- **读接口** 经 Redis + 本地缓存后,4–8 个 API 副本足够(单副本可撑 1,500+ QPS)。
- **进度上报不直接打库**:写 Redis,后台任务按 `user+drama` 合并后批量 UPSERT,落库压力降到 100 QPS 量级。
- **埋点与计数走消息队列**,消费者聚合后写回,不用事务强一致。
### 3.3 存储与转码
按第一阶段 300 部 x 60 集 x 单集 2 分钟估算:
| 项 | 计算 | 容量 |
| --- | --- | --- |
| 原片(1080p) | 18,000 集 x ~50 MB | ~0.9 TB |
| 转码产物(4 档 HLS) | 18,000 集 x ~95 MB | ~1.7 TB |
| 封面/截图/字幕 | — | ~0.1 TB |
| **合计** | | **约 3 TB** |
转码算力:单集 1080p 转 4 档约 8 分钟机时,首月入库 18,000 集约 2,400 小时;
100 核并行约 24 小时跑完。**按需弹性即可,不需要常驻转码集群**,但必须排队限流,避免抢在线服务的 CPU。
### 3.4 成本与单位经济性
假设 DAU 30 万、人均日观看 40 分钟、平均码率 0.88 Mbps:
| 项 | 月度量/成本 | 说明 |
| --- | --- | --- |
| CDN 流量 | 约 79 TB/天,约 2,300 TB/月,**$50k–75k** | 按东南亚 $0.022–0.032/GB,**最大成本项** |
| 对象存储 | 3 TB,约 $60–100 | 冷热分层后更低 |
| 数据库 / Redis / MQ | $300–800 | 主从 + 只读副本 + 集群缓存 |
| API 与后台计算 | $400–1,200 | 4–8 副本 + 弹性转码 |
| 短信 OTP | $500–2,000 | 按 $0.02–0.08/条,必须频控 |
| 监控 / 日志 / APM | $100–300 | |
| **合计** | **约 $52k–79k/月** | CDN 占绝大部分可变成本 |
单位经济性(决定商业模型是否成立):
- 单集 2 分钟、0.88 Mbps,约 13 MB 流量,CDN 成本约 **$0.0004**
- 单集解锁(按 30 金币约 $0.03),视频成本约占解锁收入 **1.5%**
推论:
1. 金币解锁模式下视频成本可忽略;真正的压力来自第二阶段的广告模式(ARPU 低一个数量级时 CDN 会变成主要成本)。
2. 因此第一阶段就要把平均码率、缓存命中率、CDN 单价做成**看板与告警**(单位播放成本 $/千次播放)。
3. 把平均码率从 0.88 压到 0.6 Mbps(更多 360p、更激进的 ABR 起播策略)可直接省掉约 30% 的 CDN 账单,比任何架构优化都有效。
## 4. 部署与网络拓扑
```
印尼用户 (iOS / Android)
| 1. 接口请求(JSON, HTTPS)
v
CDN / 全球加速 --- 2. 视频分片与静态资源(HLS,签名 URL,命中率 >=98%)
| ^
| 回源(约 2%) |
v |
区域入口 (Jakarta) |
|- API 网关:TLS、限流、鉴权、WAF |
|- app-api x4-8(K8s HPA) |
|- admin-api x2(可放新加坡/内网,仅内部使用) |
|- worker(转码调度、计数聚合、对账、清理) |
|
|- PostgreSQL(主 + 只读副本)
|- Redis(集群:缓存 / 票据 / 进度缓冲 / 限流)
|- 消息队列(转码任务、埋点、计数、对账事件)
|- 对象存储 OSS(S3 兼容)--- 转码产物 -----------+
```
**区域选择**:首选**雅加达**(阿里云 / 腾讯云 / AWS 均有区域),退路是新加坡
(ap-southeast-1,到雅加达 RTT 约 20–30ms,仍可接受)。依据是到用户 RTT、CDN 覆盖与本地 PoP 价格、数据驻留要求。
**为什么要 API 网关**:限流、IP 黑名单、WAF、灰度都放在网关层,业务服务不必各自实现一遍。
**国内团队访问生产**:运营后台走独立入口 + 内网跳板,不要与 C 端出口混用。
## 5. 服务划分与演进
第一阶段**保持模块化单体**,按业务域划清边界,为后续拆分留缝:
| 模块 | 职责 | 未来拆分优先级 |
| --- | --- | --- |
| user | 账号、凭证、会话、资料、风控 | 中 |
| content | 短剧、剧集、分类、标签、榜单 | 低 |
| media | 上传、转码、媒资、播放票据 | **高**(与 CDN/转码强绑定) |
| playback | 进度、记录、收藏、下载授权 | 中 |
| interaction | 评论、点赞、举报、分享 | 低 |
| commerce | 商品、订单、钱包、权益、对账 | **高**(支付需审计隔离) |
| creator | 创作者入驻、内容提交、数据看板 | 中 |
| admin/system | RBAC、菜单、审计、配置 | 低 |
拆分只在这两个条件下发生:**独立扩缩容需求**或**合规隔离需求**。其余情况一律不加服务。
## 6. 数据模型与存储
核心表(现有 `sql/schema.sql` 已有基础,"新增"为需要补充的部分):
- 内容域:`drama`、`drama_episode`、`drama_category`、`content_banner`,已具备。
- 媒资域:`media_asset`、`media_upload_task` 已具备;**新增** `transcode_task`、`media_rendition`(多码率产物)。
- 用户域:**新增** `app_user`、`user_credential`、`user_session`、`user_profile`(当前只有后台管理员表)。
- 播放域:**新增** `watch_progress`(user+drama 唯一)、`watch_history`(按月分区)、`user_favorite`、`playback_ticket`。
- 互动域:**新增** `comment`、`comment_reply`、`user_like`、`report`、`blocklist`。
- 交易域:**新增** `product`、`order`、`order_event`、`wallet`、`coin_ledger`、`entitlement`、`receipt`。
- 创作者域:**新增** `creator`、`creator_document`、`content_submission`、`audit_record`。
存储策略:
- 大表分区:`watch_history`、`order_event`、`coin_ledger`、`playback_event` 按月分区,保留 12–24 个月。
- 冷热分离:长期不播放的媒资转低频/归档存储,源站只保留热内容。
- 读写分离:报表、导出、后台检索走只读副本,避免影响 C 端。
- 连接池:Hikari 每副本 20–30 连接,按副本数 x 连接数留足 PG `max_connections` 余量。
## 7. 关键链路设计
### 7.1 播放与地址签发(第一阶段最重要的改造)
1. 客户端请求 `/app/episodes/{id}/play`,服务端判定权益(已购 > 活动免费 > 免费集 > 试看 > 金币可扣 > 需充值)。
2. 可播放时签发**短时效签名 URL**(建议 15 分钟),签名绑定 `episodeId + userId + 清晰度 + 过期时间`。
3. 返回 HLS master playlist 地址(CDN 域名),CDN 校验签名后才回源。
4. 签名校验失败、过期或用户被吊销,直接 403,客户端提示重新获取。
5. 防盗链:Referer/UA 白名单 + 单账号同时在播设备数限制(建议 1–2 台)。
关键点:客户端拿不到可长期复用的裸地址,也不会出现"服务端扛视频"的情况。
### 7.2 上传与转码流水线
```
创作者分片直传 OSS(STS 临时凭证)
-> 合并校验(MD5 / 时长 / 编码探测)
-> 写 transcode_task(排队)
-> worker 拉起 FFmpeg 或云转码:4 档 HLS + 封面 + 字幕封装
-> 回调写 media_rendition
-> CDN 预热
-> 状态回写创作者中心(可发布)
```
失败重试 3 次并记录原因;转码队列与在线服务隔离(独立节点池或命名空间),避免抢占资源。
### 7.3 支付与权益
1. 客户端拿到商店票据后提交 `/app/orders/verify`。
2. 服务端**必须**再调 Apple / Google 校验接口二次验证,不信任客户端上报。
3. 订单状态机幂等:以 `(platform, transactionId)` 唯一键去重,重复回调只返回既有结果。
4. 权益发放与订单在同一事务;发放失败进补偿队列,T+1 内自动修复并留痕。
5. 退款与撤销事件(App Store Server Notifications / Google RTDN)触发权益回收或欠款标记,写 `coin_ledger`。
6. 每日对账:拉取商店账单与本地订单比对,差异进入人工处理队列并告警。
### 7.4 观看进度与记录
- 客户端每 10s 上报一次;服务端写 Redis(`progress:{user}:{drama}`),不直接写库。
- 后台任务每 30s 把 Redis 中的变更按剧合并,批量 UPSERT 到 `watch_progress`(每用户每剧一行)。
- `watch_history` 按事件追加、按月分区,用于"最近观看"与后续推荐。
- 冲突规则:以 `updatedAt` 较新者为准;同秒冲突取进度较大者,避免进度倒退。
### 7.5 计数与埋点
播放数、完播数、点赞数一律走消息队列,消费者按窗口聚合后定时写回计数表。
读取计数走缓存,允许秒级最终一致,不允许为了强一致把写压力压到主库。
## 8. 高并发与稳定性手段
| 手段 | 落点 |
| --- | --- |
| 多级缓存 | 客户端本地缓存 > CDN > Redis > Caffeine 本地缓存 > PG |
| 读缓存 | 首页/详情/分类结果缓存 30–300s,运营改动通过版本号主动失效 |
| 异步化 | 进度、计数、埋点、转码、对账全部异步 |
| 合批 | 进度上报合并写、计数聚合写、日志批量落盘 |
| 限流 | 网关按 IP/设备/用户三维限流,重点保护 OTP 与票据签发 |
| 熔断降级 | 短信通道、商店校验、审核供应商失败时降级并告警;播放链路的降级是"降码率"而不是"不可播" |
| 幂等 | 订单、回调、注册、上传合并全部幂等键化 |
| 游标分页 | 列表接口用 cursor 而非深 offset,避免大偏移查询 |
| 连接管理 | 视频分片走 CDN,API 走 HTTP/2 keep-alive,PG 连接池按副本配额 |
| 压测门禁 | 上线前通过 1 万并发播放 + 1,000 QPS 接口压测,错误率 < 0.1% |
## 9. 可观测性与 SLO
| 指标 | 目标 | 告警阈值 |
| --- | --- | --- |
| 接口可用性 | 99.9% | 5 分钟错误率 > 1% |
| 接口延迟 P95(客户端热点接口) | < 50ms | > 100ms 持续 5 分钟 |
| 首帧 P95 | < 1.5s | > 2.5s |
| 卡顿率 | < 2% | > 4% |
| CDN 命中率 | >= 98% | < 95% |
| 单位播放成本 | 有基线并有月度预算 | 超预算 20% |
| 支付成功率 | > 97% | < 95% |
埋点与看板分四层:接口层(QPS/延迟/错误码)、播放层(起播/卡顿/完播/ERROR_CODE)、
业务层(注册/首播/解锁/支付)、成本层(CDN 流量/存储/短信条数)。
链路追踪覆盖"支付"与"播放票据签发"两条关键链路。
## 10. 安全与合规(印尼)
- **合规**:Kominfo **PSE 注册**(未注册可能被整体封锁);UU 27/2022 个人数据保护(明示同意、删除权、导出权、跨境传输依据);内容分级(R13/R17/R21)与色情、宗教、政治敏感内容零容忍。
- **应用安全**:网关 WAF;接口越权(水平 + 垂直)全量校验;密钥集中托管并支持轮换;后台敏感操作二次确认 + 审计留痕。
- **支付安全**:服务端票据二次校验;防重放(nonce + 唯一键);金额与商品以服务端配置为准。
- **内容保护**:签名 URL + 短时效 + 并发设备限制;第一阶段不上 DRM,但 URL 结构与接口要为第四阶段 DRM 预留。
- **数据分级**:手机号、邮箱、设备标识、支付标识分级存储与脱敏;日志与埋点禁止写入 PII 明文。
## 11. 上线与扩容路径
1. **环境**:dev / test / staging / prod 四套,配置外置,密钥不入库不入 Git。
2. **压测**:阶梯加压到 1 万并发播放(走真实 CDN 链路)+ 1,000 QPS 接口,记录水位与瓶颈。
3. **灰度**:先 5% 流量,观察起播、卡顿、错误率与支付成功率,再逐步放量。
4. **回滚**:镜像与配置双版本共存,一键回滚;数据库变更必须向后兼容(先加字段、再切读写、最后清理)。
5. **扩容触发线**:API 副本 CPU > 60% 或 QPS 超过设计值 70%;转码队列积压 > 30 分钟;CDN 命中率 < 95%。
6. **灾难恢复**:数据库 PITR + 每日全量备份(跨可用区),目标 RPO <= 15 分钟、RTO <= 2 小时,季度演练一次。
## 12. 与第一阶段工作包的对应关系
| 评估表模块 | 后端承担的部分 | 优先级 |
| --- | --- | --- |
| 01 基础与交付准备 | 服务骨架、OpenAPI 契约、ERD、CI/CD、环境与监控 | P0 |
| 02 用户账号与登录 | 用户域、OTP 与风控、会话与令牌、注销与导出 | P0 |
| 03 内容发现与详情 | 内容目录、搜索、分类榜单、可见性规则、缓存 | P0 |
| 04 播放器与播放控制 | **票据与签名 URL、转码与 ABR 片源、进度与续播接口、播放事件** | P0 |
| 05 记录、收藏与下载 | 进度存储与合并、收藏服务、下载授权与设备绑定 | P0 |
| 06 评论、点赞与分享 | 点赞与评论服务、内容治理联动、分享落地页接口 | P1 |
| 07 创作者中心 | 入驻与审核流、分片上传、转码流水线、媒资与状态服务 | P0 |
| 08 平台运营后台 | RBAC 与审计、内容/用户/订单管理、大盘数据接口 | P0 |
| 09 分集收费与支付 | 钱包与订单、票据校验、权益决策、退款与对账 | P0 |
| 10 质量、数据与上线 | 压测与容量报告、安全测试、埋点验收、发布与 DR Runbook | P0 |
建议的开发顺序(后端视角):
基础骨架与契约 → 账号与鉴权 → 内容目录与缓存 → 媒资上传与转码 → 票据与签名播放 →
进度与收藏 → 支付与权益 → 运营后台与治理 → 压测与上线准备。
其中"票据 + 签名 URL + 转码"是第一阶段风险最高、最不该压缩的链路。