前提:上线后不允许停机维护。本文给出可落地的部署拓扑、灰度流程与回滚预案。
| 环境 | 用途 | 规格建议 | 数据 |
|---|---|---|---|
| dev | 开发自测 | 2C4G × 1 | 造数据 |
| test | 集成测试 | 4C8G × 2 | 脱敏数据 |
| staging | 预发(与生产同规格) | 8C16G × 2 | 生产脱敏快照 |
| prod | 生产 | 8C32G × 3(最小) | 真实 |
staging 必须与生产同规格,否则压测与容量评估全部失效。
这是最容易被省钱砍掉、然后在上线当天付出代价的一项。
┌──────────────┐
│ SLB / ALB │ ← 公网入口,多可用区
└──────┬───────┘
│
┌────────────┴────────────┐
│ WAF 3.0 │
└────────────┬────────────┘
│
┌─────────────────┴──────────────────┐
│ ACK 集群(3 节点,多可用区) │
│ ┌──────────────────────────────┐ │
│ │ gateway (2 副本) │ │
│ │ ├─ 鉴权 / 限流 / 租户识别 │ │
│ │ └─ 灰度路由 │ │
│ ├──────────────────────────────┤ │
│ │ business-svc (3 副本) │ │ ← 房源/合同/计费/工单
│ │ integration-svc (2 副本) │ │ ← 支付/OTA/签章/AR 集成
│ │ agent-svc (2 副本) │ │ ← Agent 能力
│ │ job-worker (2 副本) │ │ ← 账单生成/对账/outbox 投递
│ └──────────────────────────────┘ │
└─────────────────┬──────────────────┘
│
┌──────────┬───────────┼────────────┬──────────────┐
│ │ │ │ │
┌───┴────┐ ┌───┴────┐ ┌────┴─────┐ ┌────┴────┐ ┌──────┴──────┐
│RDS PG │ │Redis │ │RocketMQ │ │ OSS │ │Elasticsearch│
│主+2只读│ │集群版 │ │专业版 │ │+ CDN │ │ 3 节点 │
│多可用区│ │双副本 │ │ │ │ │ │ │
└────────┘ └────────┘ └──────────┘ └─────────┘ └─────────────┘
旁路(二期):ClickHouse(IoT/报表)
| 组件 | 规格 | 说明 |
|---|---|---|
| ACK 节点 | 8C32G × 3 | 多可用区部署,单节点故障不影响 |
| RDS PostgreSQL | 16C64G,ESSD PL1 1TB,多可用区 | 主库 |
| RDS 只读实例 | 8C32G × 2 | ①报表 ②C 端查询 |
| Redis 集群版 | 8G × 3 分片,双副本 | 会话/缓存/锁 |
| RocketMQ | 专业版,4C8G | 事务消息必需 |
| OSS | 标准存储 + CDN | 图片/AR 资源 |
| SLB/ALB | 性能保障型 | 按流量选 |
⚠️ RDS 规格宁大勿小:数据库升级需要停机或切换,
而应用层扩缩容是滚动无感的。把资源余量留在数据库上。
① 代码合并 → CI 构建镜像 → 推送 ACR
② staging 部署 → 自动化回归 + 冒烟
③ 生产灰度:
├─ 步骤1:部署 1 个新版本 Pod(占 1/4 流量)
├─ 步骤2:观察 30 分钟(错误率/延迟/业务指标)
├─ 步骤3:扩到 50% → 观察
├─ 步骤4:全量
└─ 任一步异常 → 立即缩容新版本,流量回旧版本(秒级)
④ 旧版本 Pod 保留至少 30 分钟再下线(保证长连接平滑迁移)
技术前提(缺一不可):
| 前提 | 不满足的后果 |
|---|---|
| 应用无状态(会话在 Redis) | 无法多副本,无法滚动 |
优雅关闭(preStop + SIGTERM 处理) | 请求被硬断,用户看到错误 |
| 健康检查(liveness + readiness 分离) | 新 Pod 未就绪就接流量 |
| 数据库变更向后兼容 | 新旧版本数据冲突 |
| 接口幂等 | 重试导致重复扣款 |
必须遵守 Expanding / Contracting 五步法。 以「账单增加 refunding 状态」为例:
| 阶段 | 操作 | 可回滚 | 说明 |
|---|---|---|---|
| ① Expand | 放宽 CHECK 约束,允许新值 | ✅ 随时 | 纯 DDL,秒级,不锁表 |
| ② Deploy A | 新代码可写新值,旧代码读到按 pending 处理 | ✅ 回滚代码即可 | 灰度期间两版共存 |
| ③ Migrate | 后台分批把存量数据规范化 | ✅ 幂等可重跑 | 每批 5000 行,避免长事务 |
| ④ Deploy B | 移除兼容旧状态的代码 | ✅ | 此时全量新代码 |
| ⑤ Contract | 收紧约束、删废弃列 | ❌ 不可回滚 | 必须等所有旧实例下线 |
红线清单(CI 中应自动拦截):
| 操作 | 判定 | 替代方案 |
|---|---|---|
ADD COLUMN ... NOT NULL 无默认值 | 🔴 禁止 | 先 nullable + 回填 + 加约束 |
RENAME COLUMN/TABLE | 🔴 禁止 | 新增 + 双写 + 迁移 + 删除 |
ALTER COLUMN TYPE | 🔴 禁止(大表) | 新增列 + 转换 + 切换 |
CREATE INDEX(不带 CONCURRENTLY) | 🔴 禁止 | CREATE INDEX CONCURRENTLY |
DROP COLUMN 后 2 个版本内 | 🟠 警告 | 延后删除 |
全表 UPDATE | 🔴 禁止 | 分批(LIMIT + 循环) |
VACUUM FULL | 🔴 生产禁止 | pg_repack |
LOCK TABLE | 🔴 禁止 | 调整业务逻辑 |
配套实现:
DDL/09-分布式一致性.sql中的schema_migration表带is_destructive/is_online_safe字段,应在 CI 中强制校验:
破坏性变更必须标记并走审批。
每月 1 号生成数百万张账单,这是最容易造成停机的操作。
❌ 错误做法:一个大事务跑完,锁表数分钟。
✅ 正确做法:
① 按月/租户切分任务,投递到 MQ(每租户一个消息)
② worker 并发消费,每个任务独立事务(只处理一个租户的账单)
③ 幂等:bill 表的 idempotency_key 保证重复消费不会多生成(已验证)
④ 进度可查:任务表记录每个租户的处理状态
⑤ 失败可重试:单租户失败不影响其他租户
⑥ 限流:控制并发数,避免打满数据库
关键点:账单生成必须是「幂等 + 可断点续跑」的。
月中发现问题可以随时重跑,不会重复生成。
| 场景 | 回滚手段 | 目标时间 |
|---|---|---|
| 应用异常 | 缩容新版本 Pod / 切回旧镜像 | < 1 分钟 |
| 新功能有问题 | 特性开关关闭(不发版) | < 10 秒 |
| 数据库变更引发问题 | 执行反向迁移脚本 | < 5 分钟 |
| 数据写错 | 按业务单号精确修复(不做全库回滚) | 视范围 |
| 主库故障 | RDS 自动主备切换 | 30~60 秒(有闪断) |
| 整个可用区故障 | 多可用区自动切换 | 1~3 分钟 |
必须提前准备的资产:
| 层次 | 指标 | 告警阈值 | 工具 |
|---|---|---|---|
| 数据库 | 连接数使用率 | > 80% | RDS 监控 |
| 数据库 | 主备延迟 | > 10 秒 | RDS 监控 |
| 数据库 | 慢查询数(>1s) | > 10 条/分钟 | RDS 慢日志 + ARMS |
| 数据库 | 死锁与长事务 | 长事务 > 30 秒 | 自定义告警 |
| 应用 | 接口 P99 延迟 | > 500ms | ARMS |
| 应用 | 错误率 | > 1% | ARMS |
| 业务 | 收租成功率 | < 99% | 自定义埋点 |
| 业务 | 支付回调失败数 | > 0 | 对账任务 |
| 业务 | outbox 积压量 | > 1000 | 定时查询 |
| 集成 | 第三方接口失败率 | > 5% | integration_call_log |
| 对账 | 对账差异数 | > 0 | 对账任务 |
只监控技术指标是不够的。 上面标粗的三项是业务指标:
收租成功率掉点、outbox 积压、对账差异 —— 这三项能最早发现业务级故障。
基础设施
应用
数据
tenant_id 前导索引(实测差距 1250 倍)业务
连接池大小计算(重要)
应用连接数 = Pod数 × (每Pod最大连接数)
必须满足:应用连接总数 < RDS max_connections × 0.8
例:6 个 Pod × 每 Pod 20 连接 = 120 连接
RDS 16C64G 的 max_connections 约 4000,安全
⚠️ 但连接数不是越多越好。PostgreSQL 的经验值是
活跃连接数 ≈ CPU 核数 × 2~4,超过后性能反而下降。
单 Pod 的连接池建议 10~20,靠多 Pod 横向扩展。
| 触发条件 | 演进动作 |
|---|---|
| 只读副本 CPU > 70% | 增加只读副本 |
| 主库 CPU > 50% 持续 | 读写分离细化(C 端走副本) |
| 报表拖慢主库 | 引入 ClickHouse / AnalyticDB |
| 单库 > 2TB | 按 tenant_id 分库(ShardingSphere) |
| 单个大租户资源占用过高 | 该租户迁独立库(isolation_mode 已预留) |
| 商城/直播上线 | 独立集群 + 独立库,与主业务物理隔离 |
分库的技术前提(现在就要准备):
tenant_id(已满足)tenant_id(RLS 已强制)bigint identity,分库时需改雪花 ID)第 4 点是个隐患:当前主键用
GENERATED ALWAYS AS IDENTITY,
分库后会冲突。建议一期就引入雪花 ID 或号段分配,
避免分库时大规模改主键。这是我在本设计中要提醒的改进项。
配套:../分布式架构/01-阿里云分布式架构.md