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

微服务拆分精讲 / 第 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 取模数据均匀扩容困难
一致性 HashHash 环扩容友好实现复杂
  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 网关 — 统一入口、路由、限流和认证授权。