区域团队把同城外卖系统部署上云后,经常遇到一类运维投诉:订单库 CPU 飙高时,财务导出也卡住;想单独给结算报表加只读实例,却发现导出 SQL 散落在各业务服务里。根因是订单热数据与结算冷数据混在同一库、同一连接池,扩容与备份策略无法按职责拆分。
本文从云原生部署与数据层视角,说明同城外卖场景下订单库、结算库(或 schema)、只读分析实例如何分开存;含部署拓扑、备份策略与扩容顺序,供私有化或云上项目负责人参考。示例为教学示意,交付以当期方案为准。
一、业务背景:混库时的三类运维痛
同城外卖系统首期往往只有一个 MySQL 实例,订单写入、结算导出、运营报表全走同一库。业务量上来后典型问题:
- 写入被导出拖慢:大查询锁表,骑手端订单状态更新延迟。
- 备份窗口拉长:单库体积包含大量历史完结单与结算明细,全量备份超时。
- 扩容粒度粗:只能整体升配,无法给「只读报表」单独加节点。
分开存的目标不是「多买几个库炫技」,而是写路径与读路径、热数据与归档数据可按云资源独立伸缩。
二、数据分层:三类存储职责
[订单热库 order_db] 进行中与近期完结单,高写入
[结算明细库 settle_db] 对账投影、导出明细,追加写为主
[分析只读 replica] 报表、BI、慢查询,不影响主写
[对象存储 oss] 导出 CSV 归档、大文件附件
不变量:
- 订单服务只写
order_db;结算投影任务异步写入settle_db。 - 财务导出只连
settle_db或只读 replica,禁止直连订单主库跑全表扫描。 - 完结超过 N 天的订单可归档至历史表或冷存储,热库保持可控体积。
三、云上部署拓扑(私有化同理)
┌─────────────┐
用户/商家端 ──────►│ SLB / 网关 │
└──────┬──────┘
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
order-service settle-worker admin-api
│ │ │
▼ ▼ ▼
RDS order_db RDS settle_db Redis 会话
│ │
└────────┬────────┘
▼
只读 replica(报表)
│
▼
OSS 导出归档
容器化部署时,建议 order-service 与 settle-worker 分开 Deployment:前者 HPA 跟 QPS,后者跟队列堆积。混在一个 Pod 里,导出高峰会把订单 API 一起拖死。
四、表与同步:避免双写混乱
不推荐业务代码同时 UPDATE 两个库。更稳妥是事件驱动投影:
order_db 订单完结
→ 发 MQ / 本地 outbox 事件
→ settle-worker 消费
→ INSERT settle_db.st_settle_row(幂等键 order_id)
幂等键保证重试不重复入账。费率快照字段在投影时从订单扩展表拷贝,结算库不二次计算业务规则。
4.1 核心表示意
-- order_db:热数据
CREATE TABLE ot_order (
id BIGINT PRIMARY KEY,
status VARCHAR(24) NOT NULL,
amount DECIMAL(12,2) NOT NULL,
finished_at DATETIME NULL,
KEY idx_status_finished (status, finished_at)
);
CREATE TABLE ot_order_fee_snapshot (
order_id BIGINT PRIMARY KEY,
rule_id VARCHAR(32) NOT NULL,
platform_fee DECIMAL(12,2) NOT NULL,
merchant_income DECIMAL(12,2) NOT NULL
);
-- settle_db:对账与导出
CREATE TABLE st_settle_row (
order_id BIGINT PRIMARY KEY,
platform_fee DECIMAL(12,2) NOT NULL,
merchant_income DECIMAL(12,2) NOT NULL,
exported_at DATETIME NULL,
archive_uri VARCHAR(512) NULL
);
五、备份、扩容与故障切换
| 组件 | 备份建议 | 扩容触发 |
|---|---|---|
| order_db | 高频 binlog + 日快照 | 写入延迟、连接数 |
| settle_db | 日快照 + 导出文件 OSS 生命周期 | 导出队列堆积 |
| 只读 replica | 跟主库 | 慢查询 P95 |
| OSS | 版本与生命周期 90 天 | 存储量 |
同城外卖系统在云上扩容时,优先顺序通常是:订单主库写能力 → Redis 会话 → settle-worker 副本数 → 只读 replica。先扩导出而不扩写库,往往掩盖问题。
六、安全与权限:库级隔离
- 应用账号:
order_svc仅 order_db 读写;settle_svc仅 settle_db 写;report_ro仅 replica 读。 - 禁止财务工具直连 order_db 生产账号。
- 导出大文件落 OSS,签名校验下载,不在应用服务器硬盘堆积。
七、验收清单(分开存是否生效)
- 人为在 replica 跑大查询,订单写入 P95 不明显恶化。
- 完结一单后,settle_db 在约定秒数内出现对应行。
- 重复消费投影事件,settle 行不重复。
- 导出 CSV 从 settle_db 或 replica 生成,列与
ot_order_fee_snapshot一致。 - 备份恢复演练:order_db 与 settle_db 可独立恢复到同一时间点。
- 三个数据库账号权限实测:
order_svc无法 SELECT settle 表;report_ro无法 UPDATE 任何表。 - 晚高峰叠加导出压测 30 分钟,骑手端改状态成功率不低于基线。
- OSS 导出文件带签名链接,过期后无法匿名下载;生命周期 90 天自动清理验证通过。
- 归档任务跑完后热库
ot_order行数下降,历史单仍可通过 order_id 查冷归档。 - 监控大盘能区分 order 主库 IOPS 与 settle 库队列深度,告警收件人明确。
八、常见坑
- 「分开存」只建两个 schema 但同一实例 → IO 仍互抢。
- 结算导出直连 order_db,晚高峰锁表。
- 投影失败无死信队列,财务长期缺行不自知。
- 归档策略缺失,热库三年数据全在线。
十、冷热分离与归档任务
完结超过 90 天的订单可迁至 order_archive 表或 OSS Parquet,热库保留索引摘要。归档任务跑在 settle-worker 低峰窗口,避免与晚高峰写入重叠。财务需查历史单时,通过 order_id 先查热库,再异步拉冷归档。
cron: 02:00 local
SELECT id FROM ot_order WHERE finished_at < NOW()-INTERVAL 90 DAY
→ batch move to archive
→ settle_db 保留全量(体积增长慢于订单明细)
十一、跨可用区与高可用
生产 RDS 建议同城双可用区主备;只读 replica 可与主库同区降低复制延迟。若客户要求跨区灾备,需单独评估 RPO:异步复制延迟内可能丢少量投影事件,需 outbox 重放机制。
十一之二、云上部署实操(按天推进)
第 1 天:网络与账号。 新建 VPC,划分 public / private 子网;RDS、Redis 只挂 private。为 order_svc、settle_svc、report_ro 各建独立数据库账号,权限最小化,禁止 root 进应用配置。
第 2 天:双 RDS 实例。 分别创建 order 与 settle 两个 RDS 实例(或同实例两库但首期业务量小才可选后者;日单量过万建议物理分实例)。开启自动备份与 binlog,备份窗口错开晚高峰。
第 3 天:消息与 worker。 部署 MQ(或 Redis Stream 作轻量队列),settle-worker 单独 Deployment,消费组名固定,便于监控堆积。order-service 写库成功后发投影事件,不在同一事务里双写 settle 库。
第 4 天:只读与 OSS。 给 settle 库或 order 库挂只读 replica;财务导出脚本只连 replica。导出 CSV 生成后上传 OSS,本地临时文件定时清理,避免磁盘打满。
第 5 天:压测与演练。 模拟晚高峰下单 + 同时触发导出,观察 order 主库 P95 是否稳定;故意 kill 一个 worker,验证 MQ 重投后 settle 行仍幂等。
十一之三、数据层边界(谁写谁读)
| 数据对象 | 归属库 | 写入方 | 读取方 | 禁止事项 |
|---|---|---|---|---|
| 进行中订单 | order_db | order-service | 用户端、骑手端 | 财务直连全表扫 |
| 费率快照 | order_db | 下单完结时写入 | settle 投影只读拷贝 | 结算库反算费率 |
| 对账明细行 | settle_db | settle-worker | 财务导出、报表 | 订单 API 直接 UPDATE |
| 导出归档 | OSS | export 任务 | 财务下载 | 堆在应用 VM 本地盘 |
| 会话缓存 | Redis | 网关 / API | 各 API | 把订单明细塞 Redis 当库 |
边界写进运维手册:新人接手时先看这张表,避免「图省事连一个库账号跑所有 SQL」。
十一之四、故障排查
现象:下单变慢,但 CPU 不高。 先看 RDS 连接数是否顶满、是否有慢 SQL 锁等待;再查是否有人在 order 主库跑导出。处理:导出改 replica,必要时临时限流非核心报表。
现象:财务说少单。 查 settle_db 缺行订单号,反查 MQ 死信与 worker 日志;用幂等键补投,禁止手工 INSERT 无快照字段的行。
现象:投影重复两行。 查 worker 是否多消费组重复订阅,或幂等键未建唯一索引;修复后按 order_id 去重,并加告警。
现象:备份恢复后订单有、结算无。 说明两库恢复时间点不一致;以后恢复演练必须两库同一时间点,或按 outbox 重放补齐。
11.1 连接池与 RDS 规格联动
应用侧连接池上限 × Pod 副本数不得超过 RDS max_connections 的安全阈值(通常留 30% 给运维与备份)。扩 order-api 副本前,先用公式估算总连接,必要时调大 RDS 规格或引入 RDS Proxy 类中间件。
11.2 慢 SQL 治理
导出与报表慢查询应加索引或改走 replica;禁止在 order 主库做无索引全表扫描。每周 review 慢查询日志,把 TOP 3 纳入迭代,避免峰值与导出叠加时锁等待扩散。
十二、总结
同城外卖系统上云后,订单与结算数据分开存,本质是写热读冷、职责分库、投影幂等、冷热归档四件事。云原生部署下用独立服务、独立 RDS、只读 replica 与 OSS 归档组合,比单库升配更能控制对账与峰值下单互拖。光合同城支持私有化与源码交付,上述拓扑可作为验收参照;具体规格与中间件选型以项目当期方案为准。