强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

微服务拆分精讲 / 第 05 章:数据库拆分

第 05 章:数据库拆分

代码拆分是表面,数据库拆分才是灵魂。数据不独立,微服务就是假的。


5.1 为什么数据库拆分最难

5.1.1 代码与数据的拆分难度对比

  代码拆分                      数据库拆分
  ─────────                    ──────────────
  • 接口边界清晰               • 关联查询复杂
  • 可以渐进式重构              • 数据迁移风险高
  • 失败影响可控               • 一致性保证困难
  • 自动化测试支持好            • 历史数据量大
  • IDE 支持完善               • 需要双写/同步机制

  难度:★★☆☆☆                 难度:★★★★★

5.1.2 数据库拆分面临的挑战

挑战 说明 影响
跨表 JOIN 拆分后无法直接 JOIN 查询方式彻底改变
分布式事务 跨库事务无法用本地事务 数据一致性风险
数据迁移 海量数据从旧库迁移到新库 停机时间、数据丢失风险
引用完整性 外键约束跨库无法维护 需要应用层保证
全局查询 跨库统计报表困难 需要额外方案
数据冗余 可能需要冗余部分数据 数据同步复杂

5.2 数据库拆分策略

5.2.1 三种拆分模式

┌──────────────────────────────────────────────────────────┐
│                  数据库拆分策略                             │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  模式1:共享数据库 (Anti-Pattern)                         │
│  ┌────────────────────────────────────┐                  │
│  │            共享数据库               │                  │
│  │  ┌──────┐ ┌──────┐ ┌──────┐      │                  │
│  │  │用户表│ │订单表│ │商品表│      │                  │
│  │  └──────┘ └──────┘ └──────┘      │                  │
│  └──────────┬─────────────────────────┘                  │
│        ┌────┼────┐                                       │
│        ▼    ▼    ▼                                       │
│     用户   订单   商品                                    │
│     服务   服务   服务                                    │
│                                                          │
│  模式2:Schema 分离                                       │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │ user_db  │  │ order_db │  │ product_db│              │
│  │ (同一实例) │  │ (同一实例) │  │ (同一实例) │              │
│  └──────────┘  └──────────┘  └──────────┘              │
│       ▲              ▲             ▲                    │
│       │              │             │                    │
│    用户服务       订单服务       商品服务                  │
│                                                          │
│  模式3:独立数据库                                        │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │ 用户DB   │  │ 订单DB   │  │ 商品DB   │              │
│  │ (独立实例) │  │ (独立实例) │  │ (独立实例) │              │
│  │ MySQL    │  │ MySQL    │  │ ES+Redis │              │
│  └──────────┘  └──────────┘  └──────────┘              │
│       ▲              ▲             ▲                    │
│       │              │             │                    │
│    用户服务       订单服务       商品服务                  │
└──────────────────────────────────────────────────────────┘

5.2.2 推荐的渐进式拆分路径

  共享数据库 (现状)
       │
       ▼ 阶段1:逻辑分离
  Schema 分离 (同一实例)
       │
       ▼ 阶段2:接口化
  通过 API 访问 (不再跨库查询)
       │
       ▼ 阶段3:物理分离
  独立数据库实例
       │
       ▼ 阶段4:按需选型
  不同服务使用不同类型的数据库

5.3 分库分表

5.3.1 分库策略

策略 说明 适用场景
垂直分库 按业务模块拆分数据库 微服务化拆分(本章重点)
水平分库 同一业务数据按规则分散到多个库 单表数据量超大(>5000 万行)
  垂直分库(按业务拆分)
  ┌──────────┐         ┌──────────┐  ┌──────────┐  ┌──────────┐
  │  原始DB   │  ──▶    │ 用户DB    │  │ 订单DB    │  │ 商品DB    │
  │ 用户表    │         │ 用户表    │  │ 订单表    │  │ 商品表    │
  │ 订单表    │         │ 地址表    │  │ 订单项表  │  │ 分类表    │
  │ 商品表    │         │ 认证表    │  │ 支付表    │  │ 库存表    │
  └──────────┘         └──────────┘  └──────────┘  └──────────┘

  水平分库(按数据拆分)
  ┌──────────┐         ┌──────────┐  ┌──────────┐
  │  订单DB   │  ──▶    │ 订单DB_0  │  │ 订单DB_1  │
  │ (全部订单) │         │ (0-999万) │  │(1000-2000万)│
  └──────────┘         └──────────┘  └──────────┘

5.3.2 分表策略

策略 规则 优点 缺点
Range 分表 按范围(如时间、ID区间) 扩展简单 可能数据不均
Hash 分表 按字段 Hash 取模 数据均匀 扩容困难
一致性 Hash Hash 环 扩容友好 实现复杂
  Hash 分表示例(按 user_id % 4)

  user_id = 1001 → 1001 % 4 = 1 → order_1
  user_id = 1002 → 1002 % 4 = 2 → order_2
  user_id = 1003 → 1003 % 4 = 3 → order_3
  user_id = 1004 → 1004 % 4 = 0 → order_0

  ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐
  │order_0  │  │order_1  │  │order_2  │  │order_3  │
  │id%4=0   │  │id%4=1   │  │id%4=2   │  │id%4=3   │
  └─────────┘  └─────────┘  └─────────┘  └─────────┘

5.3.3 分库分表中间件

中间件 类型 特点 适用场景
ShardingSphere 代理/嵌入 Apache 顶级项目,生态完善 Java 项目首选
MyCat 代理 独立部署,对应用透明 传统架构改造
Vitess 代理 YouTube 开源,K8s 原生 大规模 MySQL
CockroachDB 分布式DB 自动分片,兼容 PostgreSQL 新项目首选
TiDB 分布式DB 兼容 MySQL,HTAP 大数据量场景

5.4 数据同步方案

5.4.1 同步方式总览

方式 时效性 复杂度 适用场景
CDC(变更数据捕获) 准实时 数据库拆分后同步
事件驱动同步 准实时 业务事件触发同步
定时批量同步 分钟级 非实时要求的数据
双写 实时 过渡期方案
API 查询 实时 查询量少的场景

5.4.2 CDC(Change Data Capture)

CDC 通过监听数据库的变更日志(如 MySQL Binlog)来捕获数据变更,是数据库拆分后数据同步的首选方案。

  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
  │  源数据库  │    │ Debezium │    │  Kafka   │    │ 目标数据库│
  │  MySQL    │───▶│ (CDC)    │───▶│ (消息)   │───▶│ 订单DB   │
  │          │    │          │    │          │    │          │
  │ Binlog   │    │ 读取     │    │ 传输     │    │ 消费写入  │
  └──────────┘    └──────────┘    └──────────┘    └──────────┘

  延迟:< 1 秒
  可靠性:高(基于日志,不丢数据)

Debezium 配置示例

{
  "name": "mysql-source-connector",
  "config": {
    "connector.class": "io.debezium.connector.mysql.MySqlConnector",
    "database.hostname": "mysql-source",
    "database.port": "3306",
    "database.user": "cdc_user",
    "database.password": "****",
    "database.server.id": "1",
    "topic.prefix": "dbserver1",
    "database.include.list": "user_db",
    "table.include.list": "user_db.users,user_db.addresses"
  }
}

5.4.3 事件驱动同步

  用户服务                   订单服务
  ┌──────────┐              ┌──────────┐
  │          │  UserUpdated │          │
  │ 修改用户  │─────────────▶│ 更新本地  │
  │ 信息     │   (事件)     │ 用户快照  │
  └──────────┘              └──────────┘
       │                          │
       ▼                          ▼
  ┌──────────┐              ┌──────────┐
  │ 用户DB   │              │ 订单DB   │
  │ (权威数据) │              │ (冗余数据) │
  └──────────┘              └──────────┘

5.5 数据一致性方案

5.5.1 一致性模型对比

模型 说明 一致性级别 性能 复杂度
强一致性 写入后立即可读 最高 最低 最高
最终一致性 写入后一段时间可读 较高 较高
因果一致性 保证因果关系的顺序

5.5.2 跨服务查询的解决模式

问题:订单服务需要查询用户信息,但用户数据在用户服务的数据库中。

  方案1:API 调用(推荐)
  ────────────────────────
  订单服务 ──API──▶ 用户服务 ──▶ 用户DB
  优点:数据自治    缺点:网络开销、延迟

  方案2:数据冗余
  ────────────────────────
  订单服务的DB中冗余用户基本信息(userId, userName)
  通过事件订阅保持同步
  优点:查询快    缺点:数据可能不一致

  方案3:CQRS + 物化视图
  ────────────────────────
  写操作:各服务写自己的DB
  读操作:查询聚合视图(ES/数据仓库)
  优点:查询灵活    缺点:架构复杂

5.5.3 数据冗余的同步策略

  ┌──────────────┐     ┌──────────────┐
  │   用户服务    │     │   订单服务    │
  │              │     │              │
  │  更新用户    │     │  查询订单    │
  │  发布事件:   │     │  (含用户信息) │
  │  UserUpdated │     │              │
  └──────┬───────┘     └──────┬───────┘
         │                    │
         ▼                    │
    ┌─────────┐               │
    │  Kafka  │               │
    │  Topic  │               │
    └────┬────┘               │
         │                    │
         ▼                    │
    ┌──────────────┐          │
    │  订单服务     │          │
    │  消费事件     │          │
    │  更新本地     │          │
    │  用户快照     │──────────┘
    └──────────────┘

  最终一致性窗口:通常 < 1 秒

5.6 业务场景:电商平台的数据库拆分实战

5.6.1 拆分前状态

  单库(MySQL)
  ├── user 表 (500 万行)
  ├── user_address 表 (800 万行)
  ├── product 表 (200 万行)
  ├── product_sku 表 (1000 万行)
  ├── order 表 (2 亿行)
  ├── order_item 表 (5 亿行)
  ├── payment 表 (1 亿行)
  └── inventory 表 (1000 万行)

5.6.2 拆分步骤

步骤 操作 验证 回滚方案
1 创建独立 Schema (user_db, order_db, …) Schema 间无直接访问 删除 Schema
2 应用层代码改造,通过 Service 访问 所有跨模块查询改用 API 代码回滚
3 部署双写逻辑(写旧库 + 写新库) 数据一致性校验 停止双写
4 切换读路径到新库 查询结果一致性 切回旧库
5 停止写旧库 无数据丢失 恢复双写
6 物理分离数据库实例 独立运行稳定 迁移回单实例
7 清理旧表中的冗余数据 业务正常 备份恢复

5.6.3 数据迁移方案

  全量迁移 + 增量同步

  ┌───────────────┐              ┌───────────────┐
  │    源数据库    │              │    目标数据库   │
  │    MySQL      │              │    MySQL      │
  └───────┬───────┘              └───────▲───────┘
          │                              │
          │  1. 全量迁移 (DataX/Dumper)   │
          └──────────────────────────────┘
          │                              │
          │  2. 增量同步 (Debezium CDC)   │
          └──────────────────────────────┘
          │                              │
          │  3. 数据校验 (checksum)       │
          └──────────────────────────────┘
          │                              │
          │  4. 切换读写                  │
          └──────────────────────────────┘

5.7 CQRS 模式

5.7.1 命令查询职责分离

CQRS(Command Query Responsibility Segregation)将读写操作分离到不同的模型中:

  ┌────────────────────────────────────────────────────┐
  │                    CQRS 架构                        │
  ├────────────────────────────────────────────────────┤
  │                                                    │
  │   写侧 (Command)              读侧 (Query)        │
  │   ┌──────────────┐           ┌──────────────┐     │
  │   │ 命令处理器    │           │ 查询处理器    │     │
  │   │              │           │              │     │
  │   │ Command ──▶  │           │ Query ──▶    │     │
  │   │ Handler      │           │ Handler      │     │
  │   └──────┬───────┘           └──────▲───────┘     │
  │          │                          │             │
  │          ▼                          │             │
  │   ┌──────────────┐           ┌──────────────┐     │
  │   │  写数据库     │  ──同步──▶│  读数据库     │     │
  │   │  (MySQL)     │           │  (ES/Redis)  │     │
  │   └──────────────┘           └──────────────┘     │
  │                                                    │
  │   特点:模型优化写入          特点:模型优化查询    │
  │         强一致性                    高性能          │
  └────────────────────────────────────────────────────┘

5.7.2 适用场景

场景 是否适用 CQRS 说明
读写比例严重不均 ✅ 适用 读远多于写,如商品详情页
复杂查询需求 ✅ 适用 多维度查询,如报表系统
简单 CRUD ❌ 不适用 增加复杂度无收益
强一致性要求 ⚠️ 需评估 CQRS 天然是最终一致性

⚠️ 注意事项

  1. 先优化查询再拆库——很多性能问题加索引、优化 SQL 就能解决
  2. 避免分布式 JOIN——如果两个表经常 JOIN,考虑放在同一个服务
  3. 保留数据校验脚本——迁移后务必验证数据完整性
  4. 考虑时区和编码——迁移时注意字符集和时区的一致性
  5. 备份!备份!备份!——数据库操作前必须有完整的备份

📖 扩展阅读

  1. Martin Fowler - DatabasePerService — 每个服务独立数据库的权威描述
  2. Debezium Documentation — CDC 方案的首选工具
  3. Apache ShardingSphere — 分库分表中间件
  4. Designing Data-Intensive Applications — Martin Kleppmann — 数据密集型系统设计
  5. Microservices Patterns Chapter 7 — Chris Richardson — 数据拆分模式

本章小结

要点 说明
拆分路径 共享库 → Schema 分离 → 接口化 → 独立库
数据同步 CDC (Debezium) 是准实时同步的首选方案
一致性 最终一致性是常态,强一致性需要特殊处理
查询模式 CQRS + 数据冗余解决跨服务查询问题
迁移策略 全量迁移 + 增量同步 + 数据校验

📌 下一章第 06 章:API 网关 — 统一入口、路由、限流和认证授权。