分库分表(2)- 水平切分的路由选择

最后更新:2020-11-02

水平分表后,某条数据具体属于哪个切分后的子表,需要增加路由算法进行计算,这个算法会引入一定的复杂性。

目前,市面上通用的分片策略分为根据范围路由、根据hash 值路由、根据 hash 值及范围混合路由这三种。

1. 范围路由

选取有序的数据列(例如,整形、时间戳等)作为路由的条件,不同分段分散到不同的数据库表中。以最常见的用户 ID 为例,路由算法可以按照 1000000 的范围大小进行分段,1 ~ 999999 放到数据库 1 的表中,1000000 ~ 1999999 放到数据库 2 的表中,以此类推。

围路由设计的复杂点主要体现在分段大小的选取上,分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会导致单表依然存在性能问题,一般建议分段大小在 100 万至 2000 万之间,具体需要根据业务选取合适的分段大小。

范围路由的优点是可以随着数据的增加平滑地扩充新的表。例如,现在的用户是 100 万,如果增加到 1000 万,只需要增加新的表就可以了,原有的数据不需要动。

范围路由的一个比较隐含的缺点是分布不均匀,假如按照 1000 万来进行分表,有可能某个分段实际存储的数据量只有 1000 条,而另外一个分段实际存储的数据量有 900 万条。

2. Hash 路由

选取某个列(或者某几个列组合也可以)的值进行 Hash 运算,然后根据 Hash 结果分散到不同的数据库表中。同样以用户 ID 为例,假如我们一开始就规划了 3 个数据库表,路由算法可以简单地用 user_id % 3 的值来表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 1 的子表中,ID 为 10086 的用户放到编号为 0 的字表中。

Hash 路由设计的复杂点主要体现在初始表数量的选取上,表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题。而用了 Hash 路由后,增加表数量是非常麻烦的,所有数据都要重分布。

3. 根据 hash 值及范围混合路由

前面我们已经了解了范围路由和Hash路由的优缺点

  • 范围路由:不需要迁移数据,但是数据分布不均匀
  • hash路由:数据分布均匀,但是扩容迁移数据痛苦

混合路由结合了两者的优点,用hash解决数据均匀的问题,用range解决数据迁移问题。

数据的扩容代表着,路由key(如id)的值变大了,那我们先保证数据变大的时候,首先用range方案让数据落地到一个范围里面。这样以后id再变大,那以前的数据是不需要迁移的

但又要考虑到数据均匀,我们可以保证一定的范围内数据均匀。因为我们每次的扩容肯定会事先设计好这次扩容的范围大小,我们只要保证这次的范围内的数据均匀就ok了。

所以混合路由的逻辑就是:先按照范围分片,再根据 hash 值取模分片

假设我们有6个库,我们可以首先将这些库划分为2个分组,group1和group2,它们使用范围分片

  • group1:id为0到1000万的用户
  • group2:id为0到2000万的用户

首先我们通过范围定位到数据所属的组编号,在通过hash值找到组内对应的数据库编号

4. 配置路由

除了上述三种路由方案外,我们还可以使用配置路由的方式。

配置路由就是路由表,用一张独立的表来记录路由信息。同样以用户 ID 为例,我们新增一张 user_router 表,这个表包含 user_id 和 table_id 两列,根据 user_id 就可以查询对应的 table_id。

配置路由设计简单,使用起来非常灵活,尤其是在扩充表的时候,只需要迁移指定的数据,然后修改路由表就可以了。配置路由的缺点就是必须多查询一次,会影响整体性能;而且路由表本身如果太大(例如,几亿条数据),性能同样可能成为瓶颈,如果我们再次将路由表分库分表,则又面临一个死循环式的路由算法选择问题。

5. 参考资料

《从0开始学架构》

《软件架构场景实战 22 讲》

Edgar

Edgar
一个略懂Java的小菜比