mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3917 字
11 分钟
综合实战:设计一个真实系统的数据库架构
2024-12-10

前 18 章我们分别讨论了存储引擎、索引、事务、查询优化、复制、分区、分布式事务、一致性与共识——每一章都在解决一个特定问题。但真实的系统设计从来不是单点问题,而是所有问题的交织:你需要在数据建模时考虑分片策略,在选型时权衡 CAP,在缓存时考虑一致性,在容灾时考虑成本。

本章综合运用全系列知识,从零设计一个社交电商平台的数据库架构。将走过完整的设计流程:需求分析 → 数据建模 → 存储选型 → 读写分离与缓存 → 分库分表 → 分布式事务 → 搜索与推荐 → 容灾与监控 → 演进路径。每一步都会回溯到前 18 章的理论基础,让你看到知识如何落地为工程决策。

前置知识:本章是全系列的收官之作,综合运用前 18 章全部知识。建议按需回顾:数据建模参考 数据建模与 Schema 设计,选型参考 数据库选型与实践,复制参考 数据复制,分区参考 数据分区,分布式事务参考 分布式事务,分库分表参考 分库分表与 NewSQL,可靠性参考 数据库可靠性

一、需求分析#

1.1 业务功能#

社交电商平台”潮购”融合社交与电商,核心功能模块:

  • 用户系统:注册/登录、个人资料、关注/粉丝、私信
  • 商品系统:商品发布、分类管理、库存管理、SKU 管理
  • 订单系统:下单、支付、退款、物流跟踪
  • 社交系统:动态发布、点赞/评论/收藏、推荐流
  • 搜索系统:全文搜索商品、用户、内容

1.2 非功能需求#

维度指标目标值
QPS读 QPS / 写 QPS50,000 / 5,000(峰值 3 倍)
延迟核心 API P99< 100ms(读),< 500ms(写)
可用性SLA99.95%(年停机 < 4.4 小时)
数据量用户 / 商品 / 订单5000 万 / 1 亿 / 10 亿(3 年)
RPO数据丢失容忍< 1 分钟
RTO恢复时间目标< 30 分钟

1.3 系统架构总览#

graph TB subgraph 客户端["客户端"] APP[" App"] WEB[" Web"] MINI[" 小程序"] end subgraph 接入层["接入层"] GW["API Gateway<br/>限流/鉴权/路由"] LB["Load Balancer"] end subgraph 服务层["微服务层"] USER_S["用户服务"] PRODUCT_S["商品服务"] ORDER_S["订单服务"] SOCIAL_S["社交服务"] SEARCH_S["搜索服务"] end subgraph 数据层["数据层"] MYSQL["MySQL 集群<br/>核心业务数据"] REDIS["Redis 集群<br/>缓存/会话/排行榜"] ES["Elasticsearch<br/>全文搜索"] MONGO["MongoDB<br/>内容/动态"] end subgraph 基础设施["基础设施"] MQ["消息队列<br/>Kafka"] CONFIG["配置中心<br/>Nacos"] MONITOR["监控<br/>Prometheus + Grafana"] end APP --> LB --> GW WEB --> LB MINI --> LB GW --> USER_S & PRODUCT_S & ORDER_S & SOCIAL_S & SEARCH_S USER_S --> MYSQL & REDIS PRODUCT_S --> MYSQL & REDIS & ES ORDER_S --> MYSQL & REDIS SOCIAL_S --> MONGO & REDIS SEARCH_S --> ES ORDER_S -.->|"异步"| MQ SOCIAL_S -.->|"异步"| MQ PRODUCT_S -.->|"CDC"| ES style 客户端 fill:#e8eaf6,stroke:#283593 style 接入层 fill:#e0f2f1,stroke:#00695c style 服务层 fill:#fff3e0,stroke:#e65100 style 数据层 fill:#fce4ec,stroke:#880e4f style 基础设施 fill:#f3e5f5,stroke:#6a1b9a
Note

上述架构遵循 数据库选型与实践 中讨论的**多语言持久化(Polyglot Persistence)**原则——不同数据特征使用不同存储引擎,而非用一种数据库解决所有问题。

二、数据建模#

2.1 ER 图设计#

erDiagram USER ||--o{ ORDER : "下单" USER ||--o{ POST : "发布" USER ||--o{ FOLLOW : "关注/被关注" PRODUCT ||--o{ ORDER_ITEM : "购买" PRODUCT }o--|| CATEGORY : "属于" ORDER ||--|{ ORDER_ITEM : "包含" ORDER ||--o| PAYMENT : "支付" POST ||--o{ COMMENT : "评论" POST ||--o{ LIKE : "点赞" USER { bigint id PK varchar username varchar email varchar phone tinyint status timestamp created_at } PRODUCT { bigint id PK varchar name bigint category_id FK decimal price int stock tinyint status } ORDER { bigint id PK bigint user_id FK tinyint status decimal total_amount timestamp created_at } ORDER_ITEM { bigint id PK bigint order_id FK bigint product_id FK int quantity decimal unit_price }

2.2 范式与反范式取舍#

数据建模与 Schema 设计 中讨论了范式与反范式的权衡。社交电商场景的核心取舍:

设计决策范式化(3NF)反范式化本项目选择
订单中的商品信息仅存 product_id,查询时 Join冗余商品名称/价格/图片反范式——订单是历史快照,商品可能改价
用户关注数COUNT 查询 follows 表冗余 follower_count 字段反范式——高频读取,低频更新
商品分类路径递归 Join 父分类冗余全路径 path_ids反范式——避免递归查询
订单金额从 order_items 聚合冗余 total_amount反范式——避免聚合计算
Important

反范式化的核心原则:读多写少且对一致性要求不严格的数据适合冗余。订单快照是典型的反范式场景——下单时的价格必须固化,不能随商品调价而变化。

2.3 表结构设计#

用户表

CREATE TABLE users (
id BIGINT NOT NULL COMMENT '用户ID(Snowflake)',
username VARCHAR(50) NOT NULL COMMENT '用户名',
email VARCHAR(255) NOT NULL COMMENT '邮箱',
phone VARCHAR(20) DEFAULT NULL COMMENT '手机号',
password_hash VARCHAR(255) NOT NULL COMMENT '密码哈希',
avatar_url VARCHAR(512) DEFAULT NULL COMMENT '头像URL',
status TINYINT NOT NULL DEFAULT 1 COMMENT '1正常 2禁用',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_email (email),
UNIQUE KEY uk_phone (phone),
KEY idx_username (username),
KEY idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

商品表

CREATE TABLE products (
id BIGINT NOT NULL COMMENT '商品ID',
name VARCHAR(200) NOT NULL COMMENT '商品名称',
category_id BIGINT NOT NULL COMMENT '分类ID',
price DECIMAL(10,2) NOT NULL COMMENT '售价',
original_price DECIMAL(10,2) DEFAULT NULL COMMENT '原价',
stock INT NOT NULL DEFAULT 0 COMMENT '库存',
sales_count INT NOT NULL DEFAULT 0 COMMENT '销量(反范式)',
status TINYINT NOT NULL DEFAULT 1 COMMENT '1上架 2下架',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_category (category_id),
KEY idx_status_created (status, created_at),
KEY idx_sales (sales_count DESC)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

订单表

CREATE TABLE orders (
id BIGINT NOT NULL COMMENT '订单ID(Snowflake)',
user_id BIGINT NOT NULL COMMENT '买家ID',
status TINYINT NOT NULL DEFAULT 1 COMMENT '1待付 2已付 3已发 4完成 5取消',
total_amount DECIMAL(12,2) NOT NULL COMMENT '订单总金额(反范式快照)',
payment_time DATETIME DEFAULT NULL COMMENT '支付时间',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_user_created (user_id, created_at),
KEY idx_status (status),
KEY idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

2.4 编码格式选择#

数据建模与 Schema 设计 中讨论了 JSON/Protobuf/Avro 的取舍。本项目的选择:

数据编码格式理由
商品属性(动态字段)JSON(MySQL JSON 列)Schema 灵活变化,读多写少
消息队列事件Protobuf高吞吐、向后兼容、跨语言
社交动态内容BSON(MongoDB 原生)嵌套文档、Schema-free
数据同步日志Avro + Schema Registry演化兼容、与 Kafka 集成

三、存储选型#

3.1 选型矩阵#

基于 数据库选型与实践 中的选型框架,结合 存储引擎 的 B 树与 LSM 树特性分析:

存储引擎职责选型理由CAP 位置
MySQL (InnoDB)用户/商品/订单等核心业务数据ACID 事务、B+ 树范围查询、成熟生态CP(主从切换短暂不可用)
Redis (Cluster)缓存/会话/排行榜/库存预扣内存级延迟、丰富数据结构、Pub/SubAP(最终一致性)
Elasticsearch商品/内容全文搜索倒排索引、相关性排序、聚合分析AP(近实时搜索)
MongoDB社交动态/评论/消息文档模型、嵌套结构、灵活 SchemaCP(可调一致性)

3.2 CAP 权衡分析#

graph TB subgraph CAP权衡["潮购平台的 CAP 权衡"] direction TB CORE["核心业务<br/>MySQL 集群"] -->|"强一致性优先"| CP["CP 模式<br/>订单/支付不能丢"] CACHE["缓存层<br/>Redis Cluster"] -->|"可用性优先"| AP1["AP 模式<br/>缓存 miss 可容忍"] SEARCH["搜索<br/>Elasticsearch"] -->|"可用性优先"| AP2["AP 模式<br/>近实时可接受"] SOCIAL["社交<br/>MongoDB"] -->|"可调一致性"| TUNE["可调模式<br/>写关注 + 读关注"] end style CAP权衡 fill:#f5f5f5,stroke:#616161 style CP fill:#ffcdd2,stroke:#c62828 style AP1 fill:#c8e6c9,stroke:#2e7d32 style AP2 fill:#c8e6c9,stroke:#2e7d32 style TUNE fill:#fff9c4,stroke:#f9a825
Warning

不同存储的 CAP 取向不同,这意味着跨存储的一致性无法自动保证。例如 MySQL 中订单已创建,但 Redis 中库存缓存可能未更新——这就是 分布式事务一致性与共识 要解决的核心问题。

3.3 Redis 数据结构映射#

基于 Redis 深入 中讨论的数据结构特性:

业务场景Redis 结构Key 设计过期策略
用户会话Stringsession:{uid}TTL 30min
商品详情缓存Hashproduct:{pid}TTL 1h + 惰性删除
库存预扣String + Luastock:{pid}无过期
排行榜ZSetrank:daily:{date}TTL 48h
用户关注列表Setfollowing:{uid}无过期 + Cache-Aside
Feed 流List/ZSetfeed:{uid}TTL 7d

四、读写分离与缓存#

4.1 MySQL 主从复制 + 读写分离#

基于 数据复制 中的单主复制模型和 MySQL 深入 中的 GTID 复制:

graph LR subgraph 写入["写入路径"] APP_W["应用写入"] --> PROXY_W["ProxySQL/ShardingSphere"] PROXY_W --> MASTER["Master<br/>读写"] end subgraph 读取["读取路径"] APP_R["应用读取"] --> PROXY_R["ProxySQL"] PROXY_R --> SLAVE1["Slave 1<br/>只读"] PROXY_R --> SLAVE2["Slave 2<br/>只读"] PROXY_R --> SLAVE3["Slave 3<br/>只读"] end MASTER -->|"异步复制<br/>GTID"| SLAVE1 MASTER -->|"异步复制<br/>GTID"| SLAVE2 MASTER -->|"半同步"| SLAVE3 style 写入 fill:#fff3e0,stroke:#e65100 style 读取 fill:#e8f5e9,stroke:#2e7d32

读写分离的核心问题——复制延迟导致读到旧数据,在 数据复制 中已详细讨论。本项目的应对策略:

场景延迟容忍解决方案
用户查看自己的订单零容忍强制路由到 Master
商品列表浏览秒级可容忍从 Slave 读取
搜索结果秒级可容忍从 Slave 读取
库存查询零容忍Redis 预扣 + Master 确认

4.2 Redis 缓存策略#

采用 数据库性能优化 中讨论的 Cache-Aside 模式:

# Cache-Aside 读取
async def get_product(product_id: int) -> Product:
# 1. 查缓存
cache_key = f"product:{product_id}"
cached = await redis.get(cache_key)
if cached:
return Product.from_json(cached)
# 2. 缓存 miss,查数据库
product = await db.query(
"SELECT * FROM products WHERE id = %s", product_id
)
if product:
# 3. 回写缓存
await redis.setex(cache_key, 3600, product.to_json())
return product
# Cache-Aside 更新
async def update_product(product_id: int, data: dict) -> None:
# 1. 先更新数据库
await db.execute(
"UPDATE products SET name=%s, price=%s WHERE id=%s",
data["name"], data["price"], product_id
)
# 2. 再删除缓存(而非更新缓存)
await redis.delete(f"product:{product_id}")

4.3 缓存一致性方案#

方案一致性复杂度本项目选择
先更新 DB 再删缓存最终一致默认方案
先删缓存再更新 DB最终一致(有窗口)并发时可能脏读
延迟双删较强一致关键数据
基于 Canal 订阅 Binlog最终一致搜索索引同步
# 延迟双删:适用于库存等关键数据
async def update_stock(product_id: int, delta: int) -> None:
cache_key = f"stock:{product_id}"
# 1. 先删缓存
await redis.delete(cache_key)
# 2. 更新数据库
await db.execute(
"UPDATE products SET stock = stock + %s WHERE id = %s AND stock + %s >= 0",
delta, product_id, delta
)
# 3. 延迟再删一次(防止并发读回写旧值)
await asyncio.sleep(0.5) # 延迟 > 主从复制延迟
await redis.delete(cache_key)

五、分库分表#

5.1 分片策略#

基于 数据分区分库分表与 NewSQL 的理论,本项目采用垂直分库 + 水平分表组合策略:

业务域分库分表策略分片键分片数
用户user_db按 user_id 哈希user_id16
商品product_db按 product_id 哈希product_id32
订单order_db按 user_id 哈希user_id64
社交social_db按 user_id 哈希user_id16
Note

订单按 user_id 而非 order_id 分片,是因为买家查订单是最高频的查询路径。按 user_id 分片后,同一买家的所有订单在同一分片,避免跨分片查询。卖家维度的查询通过 Elasticsearch 二级索引解决。

5.2 分布式 ID — Snowflake#

分库分表与 NewSQL 中讨论了分布式 ID 方案。本项目采用 Snowflake 变体:

/**
* Snowflake ID 结构(64 bit):
* | 1 bit 符号 | 41 bit 时间戳 | 5 bit 数据中心 | 5 bit 机器 | 12 bit 序列号 |
* 每毫秒可生成 4096 个 ID,最多支持 1024 节点
*/
public class SnowflakeIdGenerator {
private final long epoch = 1704067200000L; // 2024-01-01
private final long datacenterId, workerId;
private long sequence = 0, lastTimestamp = -1L;
public synchronized long nextId() {
long ts = System.currentTimeMillis();
if (ts == lastTimestamp) {
sequence = (sequence + 1) & 0xFFF;
if (sequence == 0) ts = waitNextMillis(ts);
} else { sequence = 0; }
lastTimestamp = ts;
return ((ts - epoch) << 22) | (datacenterId << 17)
| (workerId << 12) | sequence;
}
}

5.3 分片路由配置#

# ShardingSphere 分片规则
rules:
- !SHARDING
tables:
orders:
actualDataNodes: order_db_${0..15}.orders_${0..3}
tableStrategy:
standard:
shardingColumn: user_id
shardingAlgorithmName: orders_mod
keyGenerateStrategy:
column: id
keyGeneratorName: snowflake
shardingAlgorithms:
orders_mod:
type: MOD
props:
sharding-count: 64 # 16库 × 4表
keyGenerators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 1

5.4 跨分片查询方案#

查询类型方案代价
按分片键查询直接路由到目标分片零额外代价
按非分片键精确查询建立映射表(如 order_id → user_id)多一次查询
按非分片键范围查询广播到所有分片,合并结果高代价,需限制分片数
卖家查订单Elasticsearch 二级索引近实时,毫秒级延迟
分页排序各分片并行查 Top-N,归并排序内存归并,需限制深度分页

六、分布式事务#

6.1 订单创建的分布式事务#

下单操作涉及三个服务:订单服务(创建订单)、库存服务(扣减库存)、支付服务(发起支付)。在 分布式事务 中分析了 2PC 的阻塞问题和 Saga 的补偿模式。

本项目采用 Saga 模式——牺牲强一致性,换取高可用性:

sequenceDiagram participant Client participant Order as 订单服务 participant Inventory as 库存服务 participant Payment as 支付服务 participant MQ as 消息队列 Client->>Order: 创建订单 Order->>Order: 1. 写入订单(状态=待支付) Order->>MQ: 发布 OrderCreated 事件 MQ->>Inventory: 消费 OrderCreated Inventory->>Inventory: 2. 预扣库存(Redis Lua) alt 库存不足 Inventory->>MQ: 发布 StockInsufficient 事件 MQ->>Order: 消费 → 取消订单 else 库存充足 Inventory->>MQ: 发布 StockReserved 事件 end MQ->>Payment: 消费 StockReserved Payment->>Payment: 3. 创建支付单 Payment->>Client: 返回支付链接 Client->>Payment: 支付完成回调 Payment->>MQ: 发布 PaymentCompleted 事件 MQ->>Order: 消费 → 更新订单状态=已支付 MQ->>Inventory: 消费 → 确认扣减库存

6.2 本地消息表保证最终一致性#

基于 分布式事务 中的本地消息表模式:

-- 每个服务本地消息表
CREATE TABLE outbox_messages (
id BIGINT NOT NULL AUTO_INCREMENT,
aggregate_id BIGINT NOT NULL COMMENT '聚合根ID(如订单ID)',
event_type VARCHAR(100) NOT NULL COMMENT '事件类型',
payload JSON NOT NULL COMMENT '事件内容',
status TINYINT NOT NULL DEFAULT 0 COMMENT '0待发 1已发 2失败',
retry_count INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_status_created (status, created_at),
KEY idx_aggregate (aggregate_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
// 订单创建 + 消息投递(同一本地事务)
@Transactional
public Order createOrder(CreateOrderCmd cmd) {
// 1. 创建订单
Order order = Order.create(cmd);
orderMapper.insert(order);
// 2. 写入本地消息表(与订单在同一事务中)
OutboxMessage msg = OutboxMessage.builder()
.aggregateId(order.getId())
.eventType("ORDER_CREATED")
.payload(JsonSerializer.serialize(order))
.status(0)
.build();
outboxMapper.insert(msg);
return order;
}
// 定时任务:扫描未发送消息,投递到 Kafka
@Scheduled(fixedDelay = 1000)
public void relayOutboxMessages() {
List<OutboxMessage> messages = outboxMapper
.selectPendingMessages(100);
for (OutboxMessage msg : messages) {
try {
kafkaTemplate.send("order-events", msg).get();
outboxMapper.updateStatus(msg.getId(), 1);
} catch (Exception e) {
outboxMapper.incrementRetry(msg.getId());
if (msg.getRetryCount() >= 5) {
outboxMapper.updateStatus(msg.getId(), 2);
alertService.send("消息投递失败: " + msg.getId());
}
}
}
}
Important

本地消息表的核心保证:业务操作与消息投递在同一数据库事务中。只要事务提交成功,消息就一定存在;只要消息存在,就一定会被投递——这就是 分布式事务 中讨论的 至少一次投递(At-Least-Once Delivery) 语义。消费者需要保证幂等性。

七、搜索与推荐#

7.1 Elasticsearch 索引设计#

{
"mappings": {
"properties": {
"id": { "type": "long" },
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart",
"fields": {
"keyword": { "type": "keyword" },
"pinyin": { "type": "text", "analyzer": "pinyin" }
}
},
"category_path": { "type": "keyword" },
"price": { "type": "scaled_float", "scaling_factor": 100 },
"sales_count": { "type": "integer" },
"suggest": { "type": "completion", "analyzer": "ik_max_word" }
}
},
"settings": {
"number_of_shards": 8,
"number_of_replicas": 1
}
}

7.2 数据同步 — Canal + Kafka#

基于 批处理与流处理 中的 CDC(Change Data Capture)模式:

graph LR subgraph MySQL["MySQL 集群"] MASTER["Master"] BINLOG["Binlog"] end subgraph CDC["CDC 管道"] CANAL["Canal Server<br/>伪装为 Slave"] KAFKA["Kafka<br/>order-changes<br/>product-changes"] end subgraph Consumers["消费者"] ES_CONSUMER["ES Consumer<br/>更新搜索索引"] REDIS_CONSUMER["Redis Consumer<br/>更新缓存"] DATA_WAREHOUSE["数仓 Consumer<br/>写入 ClickHouse"] end MASTER -->|"写入"| BINLOG BINLOG -->|"解析"| CANAL CANAL -->|"投递"| KAFKA KAFKA --> ES_CONSUMER KAFKA --> REDIS_CONSUMER KAFKA --> DATA_WAREHOUSE style MySQL fill:#fff3e0,stroke:#e65100 style CDC fill:#e3f2fd,stroke:#1565c0 style Consumers fill:#e8f5e9,stroke:#2e7d32
# Canal Instance 配置
canal.instance.master.address: mysql-master:3306
canal.instance.dbUsername: canal
canal.instance.filter.regex: "product_db\\..*,order_db\\..*"
canal.instance.tsdb.enable: true
# Kafka Topic 投递
canal.mq.servers: kafka:9092
canal.mq.topic: canal_${database}_${table}
canal.mq.partitionHash: "product_db.products:id,order_db.orders:user_id"

7.3 推荐数据流#

数据源处理方式目标存储
用户浏览/点击Flink 实时计算 → 用户画像Redis (Hash)
购买/收藏行为Spark 批处理 → 协同过滤HDFS → MySQL
社交关系图计算 → 好友推荐Neo4j → Redis
商品属性Elasticsearch → 相似推荐ES 内部

八、容灾与监控#

8.1 两地三中心架构#

基于 数据库可靠性 中的容灾方案:

graph TB subgraph 同城["同城双活"] DC1["数据中心 A<br/>Master + Slave"] DC2["数据中心 B<br/>Slave + 备 Master"] end subgraph 异地["异地灾备"] DC3["数据中心 C<br/>异步 Slave"] end subgraph 应用["应用层"] GW["API Gateway"] end GW -->|"写 + 强一致读"| DC1 GW -->|"读"| DC2 DC1 <-->|"同步复制<br/>半同步"| DC2 DC1 -->|"异步复制"| DC3 style 同城 fill:#e8f5e9,stroke:#2e7d32 style 异地 fill:#fff3e0,stroke:#e65100
指标同城双活异地灾备
RPO0(同步复制)< 1 分钟(异步复制)
RTO< 30 秒(自动切换)< 30 分钟(手动切换)
成本高(双机房全量资源)中(降级资源)
适用场景机房级故障城市级故障

8.2 备份策略#

基于 数据库可靠性 中的备份恢复策略:

备份类型频率保留周期存储位置
全量备份(Xtrabackup)每日 02:0030 天对象存储 + 异地
增量备份每 4 小时7 天对象存储
Binlog 归档实时7 天对象存储 + 异地
Redis RDB每小时3 天本地 + 对象存储
#!/bin/bash
# 全量备份脚本(Xtrabackup)
BACKUP_DIR="/data/backups/mysql/$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"
xtrabackup --backup --target-dir="$BACKUP_DIR/full" \
--user=backup --password=$BACKUP_PASS --parallel=4 --compress
# 上传到对象存储 + 清理 30 天前本地备份
rclone sync "$BACKUP_DIR" remote:mysql-backup/$(date +%Y%m%d) --transfers=4
find /data/backups/mysql -mtime +30 -exec rm -rf {} +

8.3 监控与告警#

基于 数据库可靠性数据库性能优化 的监控体系:

# Prometheus 告警规则
groups:
- name: database_alerts
rules:
- alert: MySQLReplicationLag
expr: mysql_slave_seconds_behind_master > 10
for: 2m
labels:
severity: warning
annotations:
summary: "MySQL 复制延迟超过 10 秒"
- alert: MySQLConnectionPoolExhausted
expr: mysql_connections_used / mysql_connections_max > 0.9
for: 1m
labels:
severity: critical
annotations:
summary: "MySQL 连接池使用率超过 90%"
- alert: RedisMemoryUsage
expr: redis_memory_used_bytes / redis_memory_max_bytes > 0.85
for: 5m
labels:
severity: warning
annotations:
summary: "Redis 内存使用率超过 85%"
- alert: OrderCreationLatency
expr: histogram_quantile(0.99, rate(order_create_duration_bucket[5m])) > 0.5
for: 3m
labels:
severity: critical
annotations:
summary: "订单创建 P99 延迟超过 500ms"
监控维度核心指标告警阈值
MySQLQPS/TPS、慢查询数、复制延迟、连接数慢查询 > 50/min,延迟 > 10s
Redis内存使用率、命中率、连接数、延迟命中率 < 95%,内存 > 85%
ES索引速率、搜索延迟、段合并搜索 P99 > 200ms
业务订单创建延迟、支付成功率、库存超卖率支付成功率 < 99.9%

九、架构演进路径#

没有系统一开始就是分布式的。从单机到分布式的演进,每一步都是因为前一步的方案无法满足增长的需求。在 数据库全景 中讨论了数据库的分类体系,在 数据分区 中讨论了扩展的动机——下面是潮购平台的演进路线:

flowchart TB subgraph P1["Phase 1:单机 MySQL<br/>0 → 10 万用户"] S1["单 MySQL 实例<br/>单 Redis 实例"] S1 --> B1["瓶颈:单机 QPS 上限"] end subgraph P2["Phase 2:读写分离 + 缓存<br/>10 万 → 100 万用户"] S2["MySQL 主从<br/>Redis Cluster<br/>引入 ES"] S2 --> B2["瓶颈:单表数据量过大"] end subgraph P3["Phase 3:垂直分库 + 分表<br/>100 万 → 1000 万用户"] S3["按业务域分库<br/>订单/用户水平分表<br/>MongoDB 存社交"] S3 --> B3["瓶颈:跨库事务复杂"] end subgraph P4["Phase 4:微服务 + 分布式事务<br/>1000 万 → 5000 万用户"] S4["Saga + 本地消息表<br/>Canal CDC 管道<br/>Flink 实时计算"] S4 --> B5["瓶颈:运维复杂度"] end subgraph P5["Phase 5:云原生 + NewSQL<br/>5000 万+ 用户"] S5["TiDB 替代分库分表<br/>K8s 编排<br/>Serverless 弹性"] end P1 -->|"读 QPS 不够"| P2 P2 -->|"单表过大"| P3 P3 -->|"跨库事务"| P4 P4 -->|"运维成本"| P5 style P1 fill:#e8f5e9,stroke:#2e7d32 style P2 fill:#e3f2fd,stroke:#1565c0 style P3 fill:#fff3e0,stroke:#e65100 style P4 fill:#fce4ec,stroke:#880e4f style P5 fill:#f3e5f5,stroke:#6a1b9a
阶段核心变更解决的瓶颈引入的新问题
Phase 1 → 2主从复制 + Redis 缓存读 QPS 不足复制延迟、缓存一致性
Phase 2 → 3垂直分库 + 水平分表单表数据量过大跨库 Join、分布式 ID
Phase 3 → 4Saga + 本地消息表跨库事务一致性消息幂等、补偿逻辑
Phase 4 → 5TiDB/NewSQL 替代分库分表运维复杂度迁移风险、NewSQL 成熟度
Warning

架构演进不是越快越好。每个阶段都应该穷尽当前架构的优化空间后再升级——正如 数据库性能优化 所强调的:先优化,再扩展。过早引入分布式会带来不必要的复杂度。

十、总结#

10.1 全系列知识回顾#

19 章内容,从单机到分布式,从理论到实战,形成完整的知识体系:

篇章章节核心问题本文应用
基础数据库全景数据库分类与选型框架多语言持久化策略
基础存储引擎B 树 vs LSM 树MySQL(InnoDB) vs ES(LSM) 选型
基础索引原理索引如何加速查询分片键索引、ES 倒排索引
基础事务与并发控制ACID 与隔离级别订单事务、库存并发扣减
基础查询处理与优化优化器如何选择执行计划慢查询优化、索引策略
实战MySQL 深入InnoDB 内部实现GTID 复制、行格式选择
实战PostgreSQL 深入PG 内部实现对比参考
实战Redis 深入Redis 数据结构与持久化缓存策略、排行榜、库存预扣
实战数据库性能优化系统性调优方法论缓存策略、连接池、监控体系
实战数据建模与 Schema 设计范式与编码格式反范式设计、JSON/Protobuf 选型
实战数据库选型与实践选型决策框架MySQL + Redis + ES + MongoDB
分布式数据复制复制延迟与一致性主从读写分离、半同步复制
分布式数据分区分区策略与再平衡哈希分片、跨分片查询
分布式分布式事务跨节点事务保证Saga 模式、本地消息表
分布式一致性与共识共识算法与一致性模型CAP 权衡、Raft 选举
分布式分库分表与 NewSQLSharding 与 NewSQL分片策略、Snowflake ID
衍生批处理与流处理批流数据处理Canal CDC、Flink 实时推荐
衍生数据库可靠性备份容灾与混沌工程两地三中心、备份策略、监控告警

10.2 核心要点总结#

设计维度核心决策理论基础
数据建模订单快照反范式 + 商品属性 JSON范式 vs 反范式权衡(Ch10)
存储选型MySQL + Redis + ES + MongoDB多语言持久化 + CAP(Ch11)
读写分离关键查询强制 Master + Cache-Aside复制延迟应对(Ch12)
分库分表按 user_id 哈希 + Snowflake ID分片策略 + 分布式 ID(Ch16)
分布式事务Saga + 本地消息表 + 幂等消费最终一致性(Ch14)
数据同步Canal CDC → Kafka → ES/Redis变更数据捕获(Ch17)
容灾同城双活 + 异地灾备 + 定期演练RPO/RTO 权衡(Ch18)
演进单机 → 读写分离 → 分库分表 → 微服务 → NewSQL渐进式架构(Ch1, Ch13)

数据库架构设计没有银弹——每一个决策都是在一致性、可用性、性能和成本之间的权衡。理解了前 18 章的理论基础,你就能在每一个权衡点做出有依据的决策,而不是盲目跟风。这才是本系列最大的价值:不是教你选择什么,而是教你如何选择

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

综合实战:设计一个真实系统的数据库架构
https://blog.souloss.com/posts/database/database-hands-on-practice/
作者
Souloss
发布于
2024-12-10
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时