分库分表如何数据路由

wen 网络安全 2

本文目录导读:

分库分表如何数据路由

  1. 路由的核心三要素
  2. 常见路由算法
  3. 三种常见的映射方式
  4. 实际案例:用户订单分片
  5. 路由实现的最佳实践

分库分表的数据路由,本质上就是根据某个字段(通常称为分片键 Sharding Key)的值,通过特定的算法计算出该数据应该存放在哪个数据库和哪张表中

下面从路由核心要素常见路由算法常见映射方式以及实际案例几个方面来具体说明。

路由的核心三要素

  1. 分片键(Sharding Key):路由的依据,如用户ID、订单ID、时间等,选择原则是业务中最常用、分布均匀的字段。
  2. 分片算法(Routing Algorithm):如何计算,将分片键的值映射到“库编号”和“表编号”的数学逻辑。
  3. 映射关系(Mapping):计算结果如何对应到具体的物理库和表,这是算法的具体执行。

常见路由算法

取模/哈希算法(Hash Mod)

这是最经典、最常用的方式。

  • 原理:对分片键(如用户ID)取哈希值(或直接取ID本身),然后对数据库数量(N)或表数量(M)取模。
    • 库编号 = hash(user_id) % 数据库数量
    • 表编号 = hash(user_id) % 表数量 (或 (user_id / 数据库数量) % 表数量)
  • 特点:数据分布非常均匀,但扩展困难,因为一旦增加数据库数量(如从4库变为5库),取模的基数变了,大部分数据都需要迁移。
  • 适用场景:数据量稳定,不需要频繁扩容的业务。

范围路由(Range)

  • 原理:按照分片键的值区间来划分。
    • 库1:存放 user_id 1~1000万
    • 库2:存放 user_id 1000万~2000万
    • 库3:存放 user_id 2000万~3000万
  • 特点扩展非常容易,只需要新增一个库(如 库4 存放3000万~4000万),原有数据无需迁移,但容易造成数据热点,新入库的库数据量可能很小,而最前面的库可能数据量巨大(如老用户全在库1)。
  • 适用场景:日志、时序数据(按时间分区),或者有明确自然分界的场景。

一致性哈希路由(Consistent Hash)

  • 原理:将哈希值空间组织成一个圆环,数据库节点(或虚拟节点)分布在环上,数据通过哈希值映射到环上,并顺时针找到最近的节点。
  • 特点:解决了取模法扩容难的问题。当增加或减少节点时,只有部分数据需要迁移(只影响相邻节点),但实现相对复杂,且可能存在数据倾斜(通过引入“虚拟节点”解决)。
  • 适用场景:分布式缓存(Redis集群)、需要高可用和弹性伸缩的数据库分片。

混合路由(Range + Hash)

  • 原理:先通过范围路由找到库,再通过哈希路由找到表。
    • :按时间范围(如每月一个库)。
    • :按用户ID哈希取模(如 user_id % 16),得到该月内的16张表。
  • 特点:结合了范围扩展容易和哈希分布均匀的优势,实现相对复杂。
  • 适用场景:订单、账单等同时需要时间维度和用户维度查询的业务。

三种常见的映射方式

算法计算出的“编号”如何变成真正的库名表名?有三种实现方式:

  1. 硬编码映射

    • 在配置文件中写死:库0 -> db_order_0表3 -> t_order_03
    • 优点:简单、无性能损耗。
    • 缺点:改配置需要重启,不灵活。
  2. 计算映射(最常用)

    • 直接使用算法结果拼接成库名/表名。
    • user_id % 16 得到 8,则自动拼接 db_user_8t_user_08
    • 优点:无需配置,性能极高,路由逻辑完全由算法决定。
  3. 查找映射

    • 维护一个路由表(通常用Redis或单独的数据库存储)。
    • 库编号 = 查询路由表(分片键)
    • 优点:非常灵活,可以支持自定义路由(如按VIP等级路由)、动态调整、按需迁移数据。
    • 缺点:多一次网络IO,有性能瓶颈,且路由表本身需要高可用。

实际案例:用户订单分片

假设一个中型电商,用户订单表 t_order 预计很快达到10亿级别,我们采用“库范围 + 表哈希”的混合路由(常见于订单系统)。

  • 分片策略

    • 数据库:按用户ID的范围分库(兼顾未来扩展)。
      • db_order_0:用户ID 00000000~99999999
      • db_order_1:用户ID 100000000~199999999
      • db_order_2:用户ID 200000000~299999999
    • :在每个库内,按 user_id % 16 分成16张表(t_order_00 ~ t_order_15)。
  • 路由计算(伪代码)

    // 输入:用户ID = 123456789
    // 1. 计算库编号(范围路由)
    String dbIndex = String.valueOf(userId / 100_000_000); // 123456789 / 100_000_000 = 1
    String dbName = "db_order_" + dbIndex; // 结果:db_order_1
    // 2. 计算表编号(哈希取模)
    int tableIndex = userId % 16; // 123456789 % 16 = 5
    String tableName = "t_order_" + tableIndex; // 结果:t_order_05
    // 最终SQL:
    // SELECT * FROM db_order_1.t_order_05 WHERE user_id = 123456789;
  • 关键SQL路由

    • 查询指定用户订单(路由命中):WHERE user_id = ? -> 直接定位到1库1表,性能最佳。
    • 查询全部订单(跨库查询):无分片键条件 -> 需要向 db_order_0, db_order_1, db_order_2 下的所有16张表(共48张表)发送查询,再汇总结果,这是分库分表后的性能痛点,应尽量避免。

路由实现的最佳实践

场景 推荐路由算法 推荐映射方式 说明
中小系统,数据量稳定 取模/哈希 计算映射 简单、均匀、性能好
需要频繁扩展 一致性哈希 查找映射或计算映射 最小化数据迁移影响
时序/日志数据 范围路由(时间) 计算映射 按时间分区,冷热分离,易于归档
业务复杂,需多维度查询 混合路由 (Range + Hash) 计算映射 平衡扩展性、均匀性和灵活性

核心建议

  1. 慎选分片键:选择业务查询频率最高、数据分布最均匀的字段。
  2. 避免跨库查询:设计上尽量让查询能直接“命中”某一个分片(如强制带 user_id 查询)。
  3. 引入中间件:直接处理路由逻辑非常繁琐且容易出错,建议使用成熟的中间件,如 Apache ShardingSphere(包括 JDBC 和 Proxy 模式)MyCATVitess 等,它们内部封装了配置、算法、结果合并、分布式事务等复杂逻辑。

抱歉,评论功能暂时关闭!