← 返回展示台

阿里云分布式架构设计

前提纠正:本机仅用于设计验证,生产部署在阿里云集群。
要求:分布式、可灰度升级、上线后永不停机维护。
本文所有性能数字均来自本机 PostgreSQL 17.11 实测(100 万房源/300 万租客/500 万账单),
并明确标注到阿里云生产的换算系数。


一、先回答你的问题:单库上限在哪里,要不要分库

1.1 实测数据(可信结论)

测试环境:本机 32 核 / PostgreSQL 17.11 默认配置(shared_buffers=128MB)

数据规模:100 万房源、300 万租客、500 万账单(3.2 GB)、100 个租户

测试工具:pgbench(PostgreSQL 官方)

查询场景实测延迟实测 TPS(16并发)扫描方式
主键点查房源0.076 ms211,409Index Scan
房源列表页(租户+状态+LIMIT20)0.078 ms203,941Index Scan
逾期账单统计1.4 ms~16,000Index Only Scan(0 回表)
组合业务(3 查询/事务)0.387 ms41,318—
混合读写 9:10.667 ms23,882—
纯写 UPDATE0.423 ms37,788—
出租率聚合看板66 ms241全表聚合

1.2 关键发现:索引列顺序决定生死

实测踩到并修复的两个真实缺陷(差距达 1250 倍):

问题修复前修复后提升
按房号查房源(缺 tenant_id 前导索引)72.8 ms0.042 ms1733×
房源列表页(原索引是部分索引,查询未带 deleted_at IS NULL 用不上)43.7 ms (366 TPS)0.035 ms (20万 TPS)1250×

结论:在 RLS 架构下,「每个高频查询都必须有以 tenant_id 打头的索引」是硬约束。
因为 RLS 会把 tenant_id 条件加到所有查询上,
任何不以 tenant_id 为前导列的索引都会退化为「扫描后过滤」。

另一个教训(280 倍):统计信息滞后比索引设计更致命。

同一查询,ANALYZE 前 396 ms,ANALYZE 后 1.4 ms。

优化器选错执行计划会导致灾难性性能退化。生产必须调优 autovacuum 并监控。

1.3 单库上限测算

基于实测,换算到阿里云生产(shared_buffers=8GB,NVMe SSD,预计 2-5 倍提升):

指标本机实测(默认配置)阿里云生产估算说明
点查 TPS211,00040~80 万全内存命中
混合读写 TPS23,8825~10 万更贴近真实
单表数据量500 万账单 = 3.5GB1 亿行 ≈ 70GB 仍可接受但需分区
存储上限—单实例 3~5 TB 舒适区超过需分库

⚠️ 但 TPS 不是真正的瓶颈,真正的瓶颈是:

  1. 单机故障 = 全站宕机(你要求永不停机,这一条直接否决单库)
  2. DDL 变更锁表(你要求灰度升级,ALTER TABLE 会锁表)
  3. 备份恢复窗口(3TB 库的 pg_dump 要数小时,停机不可接受)
  4. 跨可用区容灾(单实例无法做)

## 🎯 结论:分阶段分库,不是一次性上分库
| 阶段 | 房源量 | 架构 | 何时进入 |
| --- | --- | --- | --- |
| 一期 | < 30 万 | 单主库 + 1 只读副本 + 分区 | 上线即用 |
| 二期 | 30~100 万 | 1 主 2 从(读写分离)+ 分区 + 连接池 | 主库 CPU > 50% 或 副本延迟告警 |
| 三期 | > 100 万 或 单库 > 2TB | 按 tenant_id 分库(ShardingSphere) | 单库存储 > 2TB 或写入 TPS > 5 万 |
这个判断标准是量化的,不是拍脑袋。 一期不做分库的理由:
分库会失去跨库 JOIN 与事务,账单核销这类强关联业务直接崩坏。
在没到瓶颈前分库,是纯负债。


二、整体架构(阿里云)

                          ┌─────────────────────┐
                          │   SLB / ALB(四层)  │
                          └──────────┬──────────┘
                                     │
                    ┌────────────────┴────────────────┐
                    │        WAF + CDN(静态/图片)    │
                    └────────────────┬────────────────┘
                                     │
              ┌──────────────────────┴───────────────────────┐
              │              ACK(Kubernetes 集群)           │
              │  ┌────────────────────────────────────────┐  │
              │  │  Ingress(Nginx/ALB Ingress)           │  │
              │  └───────────────┬────────────────────────┘  │
              │                  │                            │
              │   ┌──────────────┴──────────────┐            │
              │   │  网关层: Spring Cloud Gateway │            │
              │   │  (鉴权/限流/租户路由/灰度)   │            │
              │   └──────────────┬──────────────┘            │
              │                  │                            │
              │  ┌───────────────┴────────────────┐          │
              │  │         业务微服务群(灰度可控)  │          │
              │  ├────────────────────────────────┤          │
              │  │ 房源服务 │ 合同服务 │ 计费服务  │          │
              │  │ 工单服务 │ IoT服务 │ 媒体服务  │          │
              │  │ Agent服务│ 用户服务│ 商城服务  │          │
              │  └───────────────┬────────────────┘          │
              └──────────────────┼───────────────────────────┘
                                 │
        ┌────────────────────────┼────────────────────────────┐
        │                        │                            │
   ┌────┴─────┐          ┌───────┴──────┐            ┌────────┴────────┐
   │ RDS PG   │          │  Redis 集群   │            │  消息队列        │
   │ 主+只读  │          │ (主从+哨兵) │            │  RocketMQ/Kafka │
   │ 多可用区 │          └──────────────┘            └────────┬────────┘
   └────┬─────┘                                              │
        │                                             ┌──────┴──────┐
        │                                             │  OSS 对象存储│
   ┌────┴──────────┐                                  └─────────────┘
   │ ClickHouse    │      ┌─────────────┐   ┌──────────────┐
   │ (IoT/行为)   │      │   ES 搜索    │   │ AnalyticDB   │
   └───────────────┘      └─────────────┘   │ (报表可选)  │
                                            └──────────────┘

阿里云产品对应关系

层次阿里云产品选型理由
负载均衡ALB / CLB支持四层七层,健康检查与灰度
容器编排ACK(Kubernetes)灰度发布的基础设施,滚动/金丝雀必需
应用防火墙WAF 3.0C 端业务必需,防刷防爬(房源爬虫是行业常态)
CDN阿里云 CDN房源图片/AR 全景图分发,必须用
数据库RDS PostgreSQL(高可用版,多可用区)主备自动切换、秒级 RPO
只读副本RDS 只读实例 × 2报表/C 端查询隔离
缓存云数据库 Redis(集群版)会话、房源缓存、分布式锁
消息队列RocketMQ 5.x事务消息(支付/OTA 场景必需)
对象存储OSS图片/视频/AR 模型,配 CDN
搜索Elasticsearch(阿里云 ES)房源多条件检索 + 地图附近搜索
时序/OLAPClickHouse(阿里云或自建)IoT 遥测、行为日志、报表
数据同步DTS主从/跨库同步、增量订阅(binlog)
容器镜像ACR镜像仓库
配置/注册MSE Nacos服务发现 + 配置中心(灰度开关)
链路追踪ARMS长链路排查(一笔账单跨多个服务)
日志SLS 日志服务集中日志 + 审计
密钥管理KMS证件号加密、支付密钥托管
密钥/证书SSL 证书服务HTTPS

三、永不停机的三个支柱

这是你要求的"上线后不能停机维护",必须从架构层面保证,而不是靠运维小心。

支柱 1:数据库变更向后兼容(Expanding / Contracting 模式)

核心原则:任何一次发布,数据库变更必须是「可被新旧两版代码同时使用」的。

以「给账单表增加一个状态值 refunding」为例:

步骤动作说明
1. Expand只放宽约束,不加新列CHECK 约束先加上新值
2. 发布代码 A新代码能写 refunding,旧代码能读(当作 pending 处理)灰度期间两版共存
3. 数据迁移后台批次把存量数据规范化不停写
4. 发布代码 B移除对旧状态的兼容—
5. Contract收紧约束、删除废弃列必须等所有旧实例下线

❌ 绝对禁止的 DDL 操作(会锁表或破坏旧代码):

危险操作后果正确做法
ALTER TABLE ... ADD COLUMN NOT NULL(无默认值)锁表 + 旧代码 INSERT 失败先加 nullable,回填,再加约束
ALTER TABLE ... RENAME COLUMN旧代码立即崩溃加新列 + 双写 + 迁移 + 删旧列
ALTER TABLE ... ALTER COLUMN TYPE全表重写,长时间锁加新列 + 双写 + 迁移
DROP COLUMN旧代码崩溃至少等 2 个发布周期
CREATE INDEX(非并发)阻塞所有写入必须 CREATE INDEX CONCURRENTLY
大表 UPDATE 全表长事务 + 膨胀分批(每批 5000 行)

本机实测佐证:我在测试时一次性插入 2500 万行,直接被超时中断并回滚。
这说明大事务在生产会造成严重后果 —— 必须分批。

支柱 2:应用层灰度发布

灰度维度(按优先级):

  1. 按租户灰度(最适合 SaaS):tenant_id % 100 < 5 的先放大客户
  2. 按比例灰度:1% → 5% → 20% → 50% → 100%
  3. 按用户/设备:内部员工先行
  4. 按地域:先一个城市

实现方式(任选其一,推荐第一个):

灰度必须能「一键回滚」,且回滚不需要发版。

支柱 3:数据库高可用与零停机运维

事项方案
主库故障RDS 多可用区,自动主备切换(通常 30~60 秒,有短暂闪断)
计划内维护RDS 有「可维护窗口」但会闪断 → 用只读副本切换规避
版本升级先在只读副本上升级验证 → 再切换
连接不断应用侧必须实现重连 + 重试,这是前提
备份RDS 自动备份 + 日志备份(可恢复到任意时间点)
跨地域容灾RDS 跨地域备份 + DTS 单向同步到异地只读实例

⚠️ 诚实提醒:RDS 主备切换会有 30~60 秒闪断,
严格意义上不是"零停机"。要真正做到零停机,需要:
① 应用层重试 + 幂等(本章后面有专门设计)
② 读写分离时,读走副本容忍短暂失败
③ 关键写操作走 MQ 异步化,前端只需确认"已受理"


四、分布式一致性:外部系统集成(本设计的核心难点)

你要接 第三方签章、第三方 AR、OTA、第三方支付,未来还有商城、直播、团购。

这些集成的共同特征是:外部系统会超时、会重复回调、会乱序、会不可用。

这是分布式系统最容易出事的地方,也是我第一版设计完全缺失的部分。

4.1 统一设计:集成网关 + 出站事件表 + 幂等

业务服务 ──写入──> outbox_event 表(本地事务,与业务同库同事务)
                        │
                        │ 定时扫描(或 CDC 订阅 binlog)
                        ▼
                  集成网关(统一处理重试/限流/降级/签名)
                        │
                        ▼
              外部系统(支付/OTA/签章/AR)
                        │
                        │ 异步回调
                        ▼
                  inbound_callback 表(先落库,再处理)
                        │
                        ▼
                  幂等处理器 → 业务更新

三个关键组件:

  1. outbox_event(出站事件表)

解决"业务改了但消息没发出去"的问题。本地事务保证业务与事件同生共死,

再由后台任务投递(至少一次语义)。这比"先发消息再改库"可靠得多。

  1. inbound_callback(入站回调表)

第三方回调先落库再处理,杜绝"处理到一半崩溃导致状态不一致"。

记录原始报文,便于对账与追查。

  1. 幂等表 idempotency_record

所有外部请求带唯一业务号,处理前先查幂等表。

4.2 支付集成(最复杂,必须有对账)

支付的三条铁律:

  1. 不要把"支付成功"完全寄托于回调。 回调可能丢失。

必须有主动查询兜底(定时查单)。

  1. 必须有日终对账。 与支付宝/微信/银联下载对账单逐笔核对,

发现差异自动生成差错单。

  1. 金额一律用「分」为单位的整数存储和传输,绝不用浮点。

支付状态机(必须严格执行):

待支付 ──支付成功──> 已支付 ──退款──> 已退款
   │                    │
   └──超时/取消──> 已关闭  └──部分退款──> 部分退款

关键:支付成功与账单核销必须幂等。

同一笔支付回调重复到达 10 次,只能核销 1 次。

本设计已用 uq_payment_trade_no 唯一索引 + payment_allocation 唯一约束保证(已验证)。

4.3 OTA 集成(库存超卖的最大风险源)

这是最容易翻车的地方。 携程、美团、飞猪各有自己的库存与价格体系。

核心问题:多渠道同时卖同一间房 → 必然超卖。

解决方案:

平台库存(唯一真相)
     │
     ├── 推送库存到 OTA A(异步,可能失败 → 重试队列)
     ├── 推送库存到 OTA B
     └── 接受 OTA 订单(异步回调)→ 原子扣减本地库存 → 成功则确认,失败则拒单

关键设计:

⚠️ 这个场景下,拒单是正常的、不可避免的。
产品层面要提前准备拒单补偿方案(升级房型/送券),而不是追求 100% 不出错。

4.4 电子签章集成

4.5 第三方 AR 集成

换供应商时只需替换媒体资源,业务数据不动


五、C 端商城/直播/团购的架构预留

你提到未来要接商城、直播、团购。这些与租赁主业务的负载特征完全不同:

业务特征架构要求
租赁主业务(B 端为主)写多、强一致、低并发事务型数据库
商城读多、库存扣减、秒杀独立库 + Redis 库存 + 限流
直播高并发读、低延迟独立服务 + CDN + WebRTC
团购短时脉冲流量独立集群 + 弹性伸缩 + 排队

核心原则:C 端与 B 端物理隔离。

B 端(运营后台/管家/房东)  →  业务库(写主库)
C 端(租客 App/小程序)     →  读副本 + Redis + ES,绝不直连主库
商城/直播/团购              →  独立库 + 独立集群

为什么必须隔离?

一次直播带货的脉冲流量可以瞬间打满连接池,

如果和租赁主业务共用数据库,会导致收租、签合同全部不可用。

这种事故在行业内非常常见。

预留设计(本设计已考虑):


六、本机实测数据与生产换算(诚实说明)

项目本机实测换算说明
点查延迟0.076 ms本机全内存;阿里云 RDS + NVMe 接近
混合 TPS23,882生产估算 5~10 万(shared_buffers 128MB→8GB)
100 万房源表大小301 MB线性外推,1000 万房源约 3 GB
500 万账单表大小3.5 GB1 亿账单约 70 GB,必须分区
出租率聚合66 ms这是唯一需要优化到 OLAP 的场景

必须说明的口径问题:本机 shared_buffers 只有 128MB(默认值),
阿里云生产会设为物理内存 25%(8C32G → 8GB),
但不要把换算系数当成精确值,它是量级参考。
上线前必须在阿里云环境用真实数据重新压测。

唯一确定需要 OLAP 的场景:出租率/收入看板。

66ms × 100 个并发用户 = 6.6 秒,会拖垮数据库。

方案:看板数据走 ClickHouse 或每日物化视图预聚合,绝不在主库实时算。


七、关键决策速查

决策点结论依据
一期是否分库不分百万房源单库可扛;分库会毁掉跨表事务
何时分库单库 > 2TB 或写 TPS > 5 万量化门槛,非感觉
数据库高可用RDS 多可用区 + 2 只读副本永不停机的底线
永不停机怎么做Expanding/Contracting + 灰度 + 应用重试三支柱缺一不可
外部集成outbox + inbound 回调表 + 幂等表分布式一致性唯一可靠做法
支付对账必须做日终对账回调会丢,不能只信回调
OTA 防超卖本地库存为唯一真相 + 允许拒单多渠道必然不一致
C 端隔离必须物理隔离直播脉冲会打垮主业务
看板ClickHouse / 物化视图实测 66ms 实时聚合不可接受
索引硬约束高频查询必须有 tenant_id 前导实测差距 1250 倍

配套:02-分布式数据一致性设计.md(外部集成表结构)、../阿里云部署/(部署与灰度方案)

目录

  1. 一、先回答你的问题:单库上限在哪里,要不要分库
  2. 二、整体架构(阿里云)
  3. 三、永不停机的三个支柱
  4. 四、分布式一致性:外部系统集成(本设计的核心难点)
  5. 五、C 端商城/直播/团购的架构预留
  6. 六、本机实测数据与生产换算(诚实说明)
  7. 七、关键决策速查