Logo
数据分片

数据分片 #

传统的将数据集中存储至单一节点的解决方案,在性能、可用性和运维成本这三方面已经难于满足海量数据的场景。

从性能方面来说,由于关系型数据库大多采用 B+ 树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降; 同时,高并发访问请求也使得集中式数据库成为系统的最大瓶颈。

从可用性的方面来讲,服务化的无状态性,能够达到较小成本的随意扩容,这必然导致系统的最终压力都落在数据库之上。 而单一的数据节点,或者简单的主从架构,已经越来越难以承担。数据库的可用性,已成为整个系统的关键。

从运维成本方面考虑,当一个数据库实例中的数据达到阈值以上,对于 DBA 的运维压力就会增大。 数据备份和恢复的时间成本都将随着数据量的大小而愈发不可控。

在传统的关系型数据库无法满足互联网场景需要的情况下,将数据存储至原生支持分布式的 NoSQL 的尝试越来越多。 但 NoSQL 对 SQL 的不兼容性以及生态圈的不完善,使得它们在与关系型数据库的博弈中始终无法完成致命一击,而关系型数据库的地位却依然不可撼动。

概述 #

SphereEx-DBPlusEngine 分片插件可最小化透明化分库分表所带来的影响,可让用户像使用一个数据库一样使用水平分片之后的数据库集群,目前可对多种数据库实现分片方案。

基本概念 #

#

表是透明化数据分片的关键概念。SphereEx-DBPlusEngine 通过提供多样化的表类型,适配不同场景下的数据分片需求。

  • 逻辑表

相同结构的水平拆分数据库(表)的逻辑名称,是 SQL 中表的逻辑标识。 例:订单数据根据主键尾数拆分为 10 张表,分别是 t_order_0t_order_9,他们的逻辑表名为 t_order

  • 真实表

在水平拆分的数据库中真实存在的物理表。 即上个示例中的 t_order_0t_order_9

  • 表关系

指分片规则一致的一组分片表。 使用绑定表进行多表关联查询时,必须使用分片键进行关联,否则会出现笛卡尔积关联或跨库关联,从而影响查询效率。 例如:t_order 表和 t_order_item 表,均按照 order_id 分片,并且使用 order_id 进行关联,则此两张表互为绑定表关系。 绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。 举例说明,如果 SQL 为:

SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在不配置绑定表关系时,假设分片键 order_id 将数值 10 路由至第 0 片,将数值 11 路由至第 1 片,那么路由后的 SQL 应该为 4 条,它们呈现为笛卡尔积:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在配置绑定表关系,并且使用 order_id 进行关联后,路由的 SQL 应该为 2 条:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

其中 t_order 表由于指定了分片条件,SphereEx-DBPlusEngine 将会以它作为整个绑定表的主表。 所有路由计算将会只使用主表的策略,那么 t_order_item 表的分片计算将会使用 t_order 的条件。

  • 广播表

指所有的分片数据源中都存在的表,表结构及其数据在每个数据库中均完全一致。 适用于数据量不大且需要与海量数据的表进行关联查询的场景,例如:字典表。

  • 单表

指所有的分片数据源中仅唯一存在的表。 适用于数据量不大且无需分片的表。

  • 全局索引表

和主分片表查询维度不同并使用该列进行分片的分片表,适用于需要提升分片表不同维度查询性能的场景,例如:主分片表按照订单号进行分片和查询,后台统计时需要根据用户 ID 进行查询。

存储资源 #

数据分片的最小单元,由数据源名称和真实表组成。 例:ds_0.t_order_0。 逻辑表与真实表的映射关系,可分为均匀分布和自定义分布两种形式。

  • 均匀分布

指数据表在每个数据源内呈现均匀分布的态势, 例如:

db0
  ├── t_order0
  └── t_order1
db1
  ├── t_order0
  └── t_order1

存储资源的配置如下:

db0.t_order0, db0.t_order1, db1.t_order0, db1.t_order1
  • 自定义分布

指数据表呈现有特定规则的分布, 例如:

db0
  ├── t_order0
  └── t_order1
db1
  ├── t_order2
  ├── t_order3
  └── t_order4

存储资源的配置如下:

db0.t_order0, db0.t_order1, db1.t_order2, db1.t_order3, db1.t_order4

分片 #

  • 分片键

用于将数据库(表)水平拆分的数据库字段。 例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段。 SQL 中如果无分片字段,将执行全路由,性能较差。 除了对单分片字段的支持,SphereEx-DBPlusEngine 也支持根据多个字段进行分片。

  • 分片算法

用于将数据分片的算法,支持 =>=<=><BETWEENIN 进行分片。 分片算法可由开发者自行实现,也可使用 SphereEx-DBPlusEngine 内置的分片算法语法糖,灵活度非常高。分片算法具体说明请参考分片算法

  • 自动化分片算法

分片算法语法糖,用于便捷的托管所有数据节点,使用者无需关注真实表的物理分布。 包括取模、哈希、范围、时间等常用分片算法的实现。

  • 自定义分片算法 提供接口让应用开发者自行实现与业务实现紧密相关的分片算法,并允许使用者自行管理真实表的物理分布。 自定义分片算法又分为:

标准分片算法:

用于处理使用单一键作为分片键的 =INBETWEEN AND><>=<= 进行分片的场景。

复合分片算法:

用于处理使用多键作为分片键进行分片的场景,包含多个分片键的逻辑较复杂,需要应用开发者自行处理其中的复杂度。

Hint 分片算法:

用于处理使用 Hint 行分片的场景。

  • 分片策略

包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。 真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。

  • 强制分片路由

对于分片字段并非由 SQL 而是其他外置条件决定的场景,可使用 SQL Hint 注入分片值。 例:按照员工登录主键分库,而数据库中并无此字段。 SQL Hint 支持通过 Java API 和 SQL 注释两种方式使用。 详情请参见强制分片路由。

  • 行表达式

行表达式是为了解决配置的简化与一体化这两个主要问题。在繁琐的数据分片规则配置中,随着数据节点的增多,大量的重复配置使得配置本身不易被维护。 通过行表达式可以有效地简化数据节点配置工作量。

对于常见的分片算法,使用 Java 代码实现并不有助于配置的统一管理。 通过行表达式书写分片算法,可以有效地将规则配置一同存放,更加易于浏览与存储。

行表达式的使用非常直观,只需要在配置中使用 ${ expression }$->{ expression } 标识行表达式即可。 目前支持数据节点和分片算法这两个部分的配置。 行表达式的内容使用的是 Groovy 的语法,Groovy 能够支持的所有操作,行表达式均能够支持。 例如:

${begin..end} 表示范围区间 ${[unit1, unit2, unit_x]} 表示枚举值,行表达式中如果出现连续多个 ${ expression }$->{ expression } 表达式,整个表达式最终的结果将会根据每个子表达式的结果进行笛卡尔组合。

例如,以下行表达式:

${['online', 'offline']}_table${1..3}

最终会解析为:

online_table1, online_table2, online_table3, offline_table1, offline_table2, offline_table3
  • 分布式主键

传统数据库软件开发中,主键自动生成技术是基本需求。而各个数据库对于该需求也提供了相应的支持,比如 MySQL 的自增键,Oracle 的自增序列等。 数据分片后,不同数据节点生成全局唯一主键是非常棘手的问题。同一个逻辑表内的不同实际表之间的自增键由于无法互相感知而产生重复主键。 虽然可通过约束自增主键初始值和步长的方式避免碰撞,但需引入额外的运维规则,使解决方案缺乏完整性和可扩展性。

目前有许多第三方解决方案可以完美解决这个问题,如 UUID 等依靠特定算法自生成不重复键,或者通过引入主键生成服务等。为了方便用户使用、满足不同用户不同使用场景的需求, SphereEx-DBPlusEngine 不仅提供了内置的分布式主键生成器,例如 UUID、SNOWFLAKE,还抽离出分布式主键生成器的接口,方便用户自行实现自定义的自增主键生成器。

  • 分布式事务
    • XA
    • BASE

其他概念 #

  • SQL 解析

分为词法解析和语法解析。 先通过词法解析器将 SQL 拆分为一个个不可再分的单词。再使用语法解析器对 SQL 进行理解,并最终提炼出解析上下文。 解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。

  • SQL 路由

根据解析上下文匹配用户配置的分片策略,并生成路由路径。目前支持分片路由和广播路由。

  • SQL 改写

将 SQL 改写为在真实数据库中可以正确执行的语句。SQL 改写分为正确性改写和优化改写。

  • SQL 执行

通过多线程执行器异步执行。

  • 结果归并

将多个执行结果集归并以便于通过统一的 JDBC 接口输出。结果归并包括流式归并、内存归并和使用装饰者模式的追加归并这几种方式。

  • 查询优化

由 Federation 执行引擎提供支持,对关联查询、子查询等复杂查询进行优化,同时支持跨多个数据库实例的分布式查询,内部使用关系代数优化查询计划,通过最优计划查询出结果。

适用场景 #

海量数据高并发的 OLTP 场景 #

由于关系型数据库大多采用 B+ 树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降。通过 SphereEx-DBPlusEngine 数据分片,按照某个业务维度,将存放在单一数据库中的数据分散地存放至多个数据库或表中,可以达到提升性能的效果,满足高并发的 OLTP 场景下的性能要求。

海量数据实时分析 OLAP 场景 #

在传统的数据库架构中,如果用户想要进行数据分析,需要先使用 ETL 工具,将数据同步至数据平台中,然后再进行数据分析,使用 ETL 工具会导致数据分析的实效性大打折扣。SphereEx-DBPlusEngine 提供静态入口以及异构语言的支持,独立于应用程序部署,适用于实时分析的 OLAP 场景。

使用前提 #

  • 服务器中已安装 SphereEx-DBPlusEngine 和数据库集群,且服务运行正常;
  • 相关使用限制需提前了解,详见下方“使用限制”内容。

使用限制 #

兼容常用的路由至单数据节点的 SQL;由于路由至多数据节点的 SQL 由于场景相对复杂,分为稳定支持、实验性(不推荐使用)支持和不支持这三种情况。

分类描述
稳定支持常规查询,子查询,分页查询,运算表达式中包含分片键
实验性支持子查询(子查询和外层未指定分片键/键值不一致),跨库关联查询
不支持CASE WHEN,Oracle 和 SQL Server 的部分分页查询

已稳定支持 #

全面支持 DML、DDL、DCL、TCL 和常用 DAL。 支持分页、去重、排序、分组、聚合、表关联等复杂查询。 支持 PostgreSQL 和 openGauss 数据库 SCHEMA DDL 和 DML 语句。详情参考兼容性列表

常规查询

  • SELECT 主语句
SELECT select_expr [, select_expr ...] FROM table_reference [, table_reference ...]
[WHERE predicates]
[GROUP BY {col_name | position} [ASC | DESC], ...]
[ORDER BY {col_name | position} [ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
  • select_expr
* | 
[DISTINCT] COLUMN_NAME [AS] [alias] | 
(MAX | MIN | SUM | AVG)(COLUMN_NAME | alias) [AS] [alias] | 
COUNT(* | COLUMN_NAME | alias) [AS] [alias]
  • table_reference
tbl_name [AS] alias] [index_hint_list]
| table_reference ([INNER] | {LEFT|RIGHT} [OUTER]) JOIN table_factor [JOIN ON conditional_expr | USING (column_list)]

子查询

子查询和外层查询同时指定分片键,且分片键的值保持一致时,由内核提供稳定支持。

例如:

SELECT * FROM (SELECT * FROM t_order WHERE order_id = 1) o WHERE o.order_id = 1;

用于分页的子查询,由内核提供稳定支持。

例如:

SELECT * FROM (SELECT row_.*, rownum rownum_ FROM (SELECT * FROM t_order) row_ WHERE rownum <= ?) WHERE rownum > ?;

分页查询

完全支持 MySQL、PostgreSQL、openGauss 子查询。

MySQL、PostgreSQL 和 openGauss 都支持 LIMIT 分页,无需子查询:

SELECT * FROM t_order o ORDER BY id LIMIT ? OFFSET ?

运算表达式中包含分片键

当分片键处于运算表达式中时,无法通过 SQL 字面 提取用于分片的值,将导致全路由。 例如,假设 create_time 为分片键:

SELECT * FROM t_order WHERE to_date(create_time, 'yyyy-mm-dd') = '2019-01-01';

实验性支持 #

实验性支持特指使用 Federation 执行引擎提供支持。 该引擎处于快速开发中,用户虽基本可用,但仍需大量优化,是实验性产品。

子查询

子查询和外层查询未同时指定分片键,或分片键的值不一致时,由 Federation 执行引擎提供支持。

例如:

SELECT * FROM (SELECT * FROM t_order) o;

SELECT * FROM (SELECT * FROM t_order) o WHERE o.order_id = 1;

SELECT * FROM (SELECT * FROM t_order WHERE order_id = 1) o;

SELECT * FROM (SELECT * FROM t_order WHERE order_id = 1) o WHERE o.order_id = 2;

跨库关联查询

当关联查询中的多个表分布在不同的数据库实例上时,由 Federation 执行引擎提供支持。 假设 ’t_order’ 和 ’t_order_item’ 是多数据节点的分片表,并且未配置绑定表规则,’t_user’ 和 ’t_user_role’ 是分布在不同的数据库实例上的单表,那么 Federation 执行引擎能够支持如下常用的关联查询:

SELECT * FROM t_order o INNER JOIN t_order_item i ON o.order_id = i.order_id WHERE o.order_id = 1;

SELECT * FROM t_order o INNER JOIN t_user u ON o.user_id = u.user_id WHERE o.user_id = 1;

SELECT * FROM t_order o LEFT JOIN t_user_role r ON o.user_id = r.user_id WHERE o.user_id = 1;

SELECT * FROM t_order_item i LEFT JOIN t_user u ON i.user_id = u.user_id WHERE i.user_id = 1;

SELECT * FROM t_order_item i RIGHT JOIN t_user_role r ON i.user_id = r.user_id WHERE i.user_id = 1;

SELECT * FROM t_user u RIGHT JOIN t_user_role r ON u.user_id = r.user_id WHERE u.user_id = 1;

不支持

不支持的 SQL:

  • CASE WHEN 中包含子查询
  • CASE WHEN 中使用逻辑表名(请使用表别名)
  • INSERT INTO tbl_name (col1, col2, …) SELECT * FROM tbl_name WHERE col3 = ?(SELECT 子句不支持 * 和内置分布式主键生成器)
  • REPLACE INTO tbl_name (col1, col2, …) SELECT * FROM tbl_name WHERE col3 = ?(SELECT 子句不支持 * 和内置分布式主键生成器)
  • SELECT MAX(tbl_name.col1) FROM tbl_name(查询列是函数表达式时,查询列前不能使用表名,可以使用表别名)

CASE WHEN

以下 CASE WHEN 语句不支持:

  • CASE WHEN 中包含子查询
  • CASE WHEN 中使用逻辑表名(请使用表别名)

注意事项 #

  1. 分片表 Join 问题,多表关联中涉及的小表可以配置为广播表,可提升关联效率;
  2. 分布式主键问题,在 Sharding 场景下,自增字段无法保证全局唯一性,因此需搭建发号器来生成全局唯一的 ID,如 Snowflake、UUID 或 NanoID 等;
  3. 分布式事务问题:SphereEx-DBPlusEngine 提供了 LOCAL,XA,BASE 三种模式的分布式事务的,GTM 暂时没有实现;
  4. 分片规则中配置的真实表、分片列和分布式序列需要和数据库中的列保持大小写一致。

原理介绍 #

SphereEx-DBPlusEngine 内核处理流程包括标准的 SQL ParserSQL Binder 模块,这两个模块用于 SQL 具体特征的识别,并根据其结果,将 SQL 执行流程分为 Simple Push Down Engine 和 SQL Federation Engine。

关于原理内容的详细介绍,请参考内核原理-数据分片

使用指南 #

  1. 分片设计:分片键

在开始分片之前,首先需要确定分片键(Sharding Key)。很多例子会使用自增 ID 或时间戳字段进行拆分,这些不是绝对的;更好的建议是结合业务,进行选择。可考虑按如下策略进行选择:

  • 对系统中执行的 SQL 进行统计分析,选择出需要分片那个表中最频繁被使用到或最为重要的字段类分片。这其中可能包含一些来自 OLAP 类的查询,可将此部分 SQL 排除在外。

  • 如业务非常复杂,存在多种维度划分可能,可以考虑适当多维度字段拆表。必要时可考虑通过业务逻辑进行拆分(而非简单字段划分)。

  • 如涉及到多维度拆分,需维护一定数据冗余。一般主维度可以由程序写入,次维度则异步写入,保证两者最终一致。

  • 如涉及到关联查询的问题,可配合分片策略来实施。如考虑 ER 表、广播表等配合策略。如非常复杂,也可考虑在 OLAP 系统中提供支持。数据同步策略上,可考虑推拉结合。

  • 最终选择拆分字段,应是稳定的,不变更的,避免跨片移动问题。

  1. 分片设计:分片策略

针对分片算法,常见的算法有 LIST、RANGE、HASH 等。根据各拆分算法特点,可进行选择。若范围均匀可采用 HASH,冷热数据明显可采用 RANGE 等。同时可配合一些特性化设计,如采用二级映射方式解决扩缩容问题、特征编码字段满足多特征拆分等。

HashListRange
优点分布及访问均匀人工可以识别人工可以识别
有序,范围查询友好
收缩/收缩相对友好
缺点无序,范围查询需跨片
人工无法识别分布
收缩/扩容不友好
容易分布不均匀数据分布/数据访问不均匀

这其中最为常见的就是 HASH、RANGE,说明如下。

  • HASH

虽然分库分表的方案众多,但是 Hash 分库分表是最大众最普遍的方案。随机分片其实并不是随机,也遵循一定规则。通常采用 HASH 取模的方式进行分片拆分,所以有时候也称为离散分片。随机分片的数据相对均匀,不容易出现热点和并发访问的瓶颈。但涉及后面数据迁移的话,不太方便。可使用一致性 HASH 算法在很大程度上避免此问题。此外,离散分片也容易面临跨分片查询的复杂问题。

  • RANGE

通过数据的范围进行分库分表,是最朴实的一种分库方案,它也可以和其他分库分表方案灵活结合使用。当需要使用分片字段进行范围查找时,RANGE 分片策略可快速定位数据进行高效查询。大多数情况下有效避免跨分片查询的问题。在后期扩容时,也比较方便,只需要添加节点即可,无需对其他分片的数据进行迁移。但这种分布方式容易存在数据热点问题。

  1. 设计:分片数量规划

由于业务特点不同,对于单分片数量无明确限制,需结合测试结果来判定,满足效率和预留空间需求即可。

  1. 设计:分布式 ID

在分库分表环境下,数据分布在不同分片上,不能再借助数据库自增长特性直接生成,否则会造成不同分片上的数据表主键重复,可使用全局发号器来生成全局唯一的 ID,如 Snowflake、UUID 或 NanoID。

  1. 针对已上线业务,需确认是否存在不支持的语句,并适配改造

数据经过拆分后,相较于传统集中式必然面临一些 SQL 受限的问题,这里需要业务对 SQL 特点进行梳理,结合分片使用限制说明,进行适配改造。

  1. 针对已上线业务的迁移与扩容

对于新项目而言,配置好分片规则后即可直接使用。对于已上线的业务,需要对数据进行迁移,即从集中式到分布式的迁移,可参考数据迁移插件。对于二次扩容的情况,可参考弹性伸缩插件。

行表达式 #

在繁琐的数据分片规则配置中,随着数据节点的增多,大量的重复配置使得配置本身不易被维护。 通过行表达式可以有效地简化数据节点配置工作量。

对于常见的分片算法,使用 Java 代码实现并不有助于配置的统一管理。 通过行表达式书写分片算法,可以有效地将规则配置一同存放,更加易于浏览与存储。

语法说明 #

行表达式的使用非常直观,只需要在配置中使用 ${ expression }$->{ expression } 标识行表达式即可。 目前支持数据节点和分片算法这两个部分的配置。 行表达式的内容使用的是 Groovy 的语法,Groovy 能够支持的所有操作,行表达式均能够支持。 例如:

${begin..end} 表示范围区间

${[unit1, unit2, unit_x]} 表示枚举值

行表达式中如果出现连续多个 ${ expression }$->{ expression } 表达式,整个表达式最终的结果将会根据每个子表达式的结果进行笛卡尔组合。

例如,以下行表达式:

${['online', 'offline']}_table${1..3}

最终会解析为:

online_table1, online_table2, online_table3, offline_table1, offline_table2, offline_table3

配置说明 #

对于均匀分布的数据节点,如果数据结构如下:

db0
  ├── t_order0
  └── t_order1
db1
  ├── t_order0
  └── t_order1

用行表达式可以简化为:

db${0..1}.t_order${0..1}

或者

db$->{0..1}.t_order$->{0..1}

对于自定义的数据节点,如果数据结构如下:

db0
  ├── t_order0
  └── t_order1
db1
  ├── t_order2
  ├── t_order3
  └── t_order4

用行表达式可以简化为:

db0.t_order${0..1},db1.t_order${2..4}

或者

db0.t_order$->{0..1},db1.t_order$->{2..4}

对于有前缀的数据节点,也可以通过行表达式灵活配置,如果数据结构如下:

db0
  ├── t_order_00
  ├── t_order_01
  ├── t_order_02
  ├── t_order_03
  ├── t_order_04
  ├── t_order_05
  ├── t_order_06
  ├── t_order_07
  ├── t_order_08
  ├── t_order_09
  ├── t_order_10
  ├── t_order_11
  ├── t_order_12
  ├── t_order_13
  ├── t_order_14
  ├── t_order_15
  ├── t_order_16
  ├── t_order_17
  ├── t_order_18
  ├── t_order_19
  └── t_order_20
db1
  ├── t_order_00
  ├── t_order_01
  ├── t_order_02
  ├── t_order_03
  ├── t_order_04
  ├── t_order_05
  ├── t_order_06
  ├── t_order_07
  ├── t_order_08
  ├── t_order_09
  ├── t_order_10
  ├── t_order_11
  ├── t_order_12
  ├── t_order_13
  ├── t_order_14
  ├── t_order_15
  ├── t_order_16
  ├── t_order_17
  ├── t_order_18
  ├── t_order_19
  └── t_order_20

可以使用分开配置的方式,先配置包含前缀的数据节点,再配置不含前缀的数据节点,再利用行表达式笛卡尔积的特性,自动组合即可。 上面的示例,用行表达式可以简化为:

db${0..1}.t_order_0${0..9}, db${0..1}.t_order_${10..20}

或者

db$->{0..1}.t_order_0$->{0..9}, db$->{0..1}.t_order_$->{10..20}

对于只有一个分片键的使用 =IN 进行分片的 SQL,可以使用行表达式代替编码方式配置。

行表达式内部的表达式本质上是一段 Groovy 代码,可以根据分片键进行计算的方式,返回相应的真实数据源或真实表名称。

例如:分为 10 个库,尾数为 0 的路由到后缀为 0 的数据源, 尾数为 1 的路由到后缀为 1 的数据源,以此类推。用于表示分片算法的行表达式为:

ds${id % 10}

或者

ds$->{id % 10}

分片策略 #

包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。 真正可用于分片操作的是分片键 + 分片算法,也就是分片策略。

强制分片路由 #

对于分片字段并非由 SQL 而是其他外置条件决定的场景,可使用 SQL Hint 注入分片值。 例:按照员工登录主键分库,而数据库中并无此字段。 SQL Hint 支持通过 Java API 和 SQL 注释(待实现)两种方式使用。

  • 实现动机 通过解析 SQL 语句提取分片键列与值并进行分片是 SphereEx-DBPlusEngine 对 SQL 零侵入的实现方式。 若 SQL 语句中没有分片条件,则无法进行分片,需要全路由。 在一些应用场景中,分片条件并不存在于 SQL,而存在于外部业务逻辑。 因此需要提供一种通过外部指定分片结果的方式,在 SphereEx-DBPlusEngine 中叫做 Hint。
  • 实现机制 DBPlusEngine 使用 ThreadLocal 管理分片键值。 可以通过编程的方式向 HintManager 中添加分片条件,该分片条件仅在当前线程内生效。 除了通过编程的方式使用强制分片路由,SphereEx-DBPlusEngine 还可以通过 SQL 中的特殊注释的方式引用 Hint,使开发者可以采用更加透明的方式使用该功能。 指定了强制分片路由的 SQL 将会无视原有的分片逻辑,直接路由至指定的真实数据节点。

操作指南 #

DistSQL #

  1. 准备环境,完成 SphereEx-DBPlusEngine 及数据库部署;
  2. 创建逻辑库,注册存储(数据库)资源;
  3. 创建分片规则,配置数据源、分片算法及分片数量;
  4. 创建与分片规则同名的分片表;
  5. 插入测试数据,确认执行路径,完成配置。

Console #

  1. 准备环境,完成 SphereEx-Console、SphereEx-DBPlusEngine 及数据库部署;
  2. 登陆 SphereEx-Console,在“集群”-“对象”页中,点击“新建表”按钮;
  3. 在新建表的页面中,填写建表语句,然后点击下方“添加分片插件”按钮;
  4. 在分片插件页面中,对分片信息进行配置;
  5. 确认表分布情况,完成配置。

配置示例 #

以下配置示例不涉及存量数据,对于有存量数据拆分的情况,需参考数据迁移插件操作。

使用 DistSQL 配置分片 #

环境说明 #

实例IP 地址服务端口主机名备注
1DBPlusEngine 1.2.0192.168.xx.1023307dbplusengine
2MySQL 8.0.28192.168.xx.1053306mysql_0
3MySQL 8.0.28192.168.xx.1063306mysql_1

拓扑图 #

拓扑图

配置过程 #

请提前在 MySQL 两个数据源中分别创建 mysql_0mysql_1 数据库。

  1. 在 SphereEx-DBPlusEngine 创建名为 testdb 的逻辑库。
mysql> CREATE DATABASE testdb;
Query OK, 0 rows affected (0.15 sec)

mysql> SHOW DATABASES;
+--------------------+
| schema_name        |
+--------------------+
| mysql              |
| information_schema |
| performance_schema |
| sys                |
| testdb             |
+--------------------+
5 rows in set (0.00 sec)
  1. 将准备好的数据源注册到 SphereEx-DBPlusEngine 中。
mysql> USE testdb;
Database changed

mysql> REGISTER STORAGE UNIT ds_0 (
    URL="jdbc:mysql://192.168.xx.105:3306/mysql_0?serverTimezone=UTC&useSSL=false",
    USER="test",
    PASSWORD="Test@123"
), ds_1 (
    URL="jdbc:mysql://192.168.xx.106:3306/mysql_1?serverTimezone=UTC&useSSL=false",
    USER="test",
    PASSWORD="Test@123"
);
Query OK, 0 rows affected (0.60 sec)

mysql> SHOW STORAGE UNITS\G
*************************** 1. row ***************************
                           name: ds_1
                           type: MySQL
                           host: 192.168.xx.106
                           port: 3306
                             db: mysql_1
connection_timeout_milliseconds: 30000
      idle_timeout_milliseconds: 60000
      max_lifetime_milliseconds: 2100000
                  max_pool_size: 50
                  min_pool_size: 1
                      read_only: false
               other_attributes: {"dataSourceProperties":{"cacheServerConfiguration":"true","elideSetAutoCommits":"true","useServerPrepStmts":"true","cachePrepStmts":"true","rewriteBatchedStatements":"true","cacheResultSetMetadata":"false","useLocalSessionState":"true","maintainTimeStats":"false","prepStmtCacheSize":"200000","tinyInt1isBit":"false","prepStmtCacheSqlLimit":"2048","netTimeoutForStreamingResults":"0","zeroDateTimeBehavior":"round"},"healthCheckProperties":{},"initializationFailTimeout":1,"validationTimeout":5000,"leakDetectionThreshold":0,"poolName":"HikariPool-83","registerMbeans":false,"allowPoolSuspension":false,"autoCommit":true,"isolateInternalQueries":false}
*************************** 2. row ***************************
                           name: ds_0
                           type: MySQL
                           host: 192.168.xx.105
                           port: 3306
                             db: mysql_0
connection_timeout_milliseconds: 30000
      idle_timeout_milliseconds: 60000
      max_lifetime_milliseconds: 2100000
                  max_pool_size: 50
                  min_pool_size: 1
                      read_only: false
               other_attributes: {"dataSourceProperties":{"cacheServerConfiguration":"true","elideSetAutoCommits":"true","useServerPrepStmts":"true","cachePrepStmts":"true","rewriteBatchedStatements":"true","cacheResultSetMetadata":"false","useLocalSessionState":"true","maintainTimeStats":"false","prepStmtCacheSize":"200000","tinyInt1isBit":"false","prepStmtCacheSqlLimit":"2048","netTimeoutForStreamingResults":"0","zeroDateTimeBehavior":"round"},"healthCheckProperties":{},"initializationFailTimeout":1,"validationTimeout":5000,"leakDetectionThreshold":0,"poolName":"HikariPool-84","registerMbeans":false,"allowPoolSuspension":false,"autoCommit":true,"isolateInternalQueries":false}
2 rows in set (0.00 sec)
  1. 创建分片规则,并确认创建结果,此处创建分片规则的名称需要与业务表名保持一致。
mysql> CREATE SHARDING TABLE RULE t_user(
 STORAGE_UNITS(ds_0,ds_1),
 SHARDING_COLUMN=user_id,
 TYPE(NAME="hash_mod",PROPERTIES("sharding-count"="4"))
);
Query OK, 0 rows affected (0.40 sec)

mysql> SHOW SHARDING TABLE RULE t_user\G
*************************** 1. row ***************************
                            table: t_user
                actual_data_nodes:
              actual_data_sources: ds_0,ds_1
           database_strategy_type:
         database_sharding_column:
 database_sharding_algorithm_type:
database_sharding_algorithm_props:
              table_strategy_type: STANDARD
            table_sharding_column: user_id
    table_sharding_algorithm_type: hash_mod
   table_sharding_algorithm_props: sharding-count=4
              key_generate_column:
               key_generator_type:
              key_generator_props:
1 row in set (0.00 sec)

通过以上配置,t_user 表会被分为 4 个分片,存储在 2 个数据源中。其中 user_id 是分片字段,所采用的分片算法是 hash_mod

  1. 创建 t_user 表,并插入若干条记录。
mysql> CREATE TABLE `t_user` (
 `user_id` int NOT NULL,
 `order_id` int NOT NULL,
 `status` varchar(45) DEFAULT NULL,
 PRIMARY KEY (`user_id`)
);
Query OK, 0 rows affected (0.45 sec)

mysql> INSERT INTO t_user VALUES
(1,1,'active'),
(2,2,'active'),
(3,3,'active'),
(4,4,'active');
Query OK, 4 rows affected (0.20 sec)

mysql> SELECT * FROM t_user ORDER BY user_id;
+---------+----------+--------+
| user_id | order_id | status |
+---------+----------+--------+
|       1 |        1 | active |
|       2 |        2 | active |
|       3 |        3 | active |
|       4 |        4 | active |
+---------+----------+--------+
4 rows in set (0.06 sec)
  1. 使用 PREVIEW 命令对 SQL 的执行路径进行查看。
mysql> PREVIEW SELECT * FROM t_user;
+------------------+---------------------------------------------------------+
| data_source_name | actual_sql                                              |
+------------------+---------------------------------------------------------+
| ds_0             | SELECT * FROM t_user_0 UNION ALL SELECT * FROM t_user_2 |
| ds_1             | SELECT * FROM t_user_1 UNION ALL SELECT * FROM t_user_3 |
+------------------+---------------------------------------------------------+
2 rows in set (0.05 sec)
  1. 最后,在物理库中确认分片的分布情况。
--ds_0
mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| mysql_0            |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> USE mysql_0;

mysql> SHOW TABLES;
+-------------------+
| Tables_in_mysql_0 |
+-------------------+
| t_user_0          |
| t_user_2          |
+-------------------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM t_user_0;
+---------+----------+--------+
| user_id | order_id | status |
+---------+----------+--------+
|       4 |        4 | active |
+---------+----------+--------+
1 row in set (0.00 sec)

mysql> SELECT * FROM t_user_2;
+---------+----------+--------+
| user_id | order_id | status |
+---------+----------+--------+
|       2 |        2 | active |
+---------+----------+--------+
1 row in set (0.00 sec)

--ds_1
mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| mysql_0            |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> USE mysql_1;

mysql> SHOW TABLES;
+-------------------+
| Tables_in_mysql_1 |
+-------------------+
| t_user_1          |
| t_user_3          |
+-------------------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM t_user_1;
+---------+----------+--------+
| user_id | order_id | status |
+---------+----------+--------+
|       1 |        1 | active |
+---------+----------+--------+
1 row in set (0.00 sec)

mysql> SELECT * FROM t_user_3;
+---------+----------+--------+
| user_id | order_id | status |
+---------+----------+--------+
|       3 |        3 | active |
+---------+----------+--------+
1 row in set (0.00 sec)

如输出结果所示,t_user 的记录存储在 2 个数据库的 4 张分片表中。

使用 Console 配置分片 #

环境说明 #

实例IP 地址服务端口主机名备注
1DBPlusEngine 1.2.010.9.122.803307dbplusengine
2Console 1.1.010.9.122.818089console
3MySQL 8.0.2810.9.122.8213306test_2
4MySQL 8.0.2810.9.122.8213306test_3

拓扑图 #

拓扑图

配置过程 #

  1. 登陆 SphereEx-Console,在“集群”-“对象”页中,点击“新建表”按钮。

  2. 在新建表的页面中,在表单框中填写建表语句,然后点击下方“添加分片插件”按钮。

  3. 在分片插件页面中,对分片信息进行配置。

在此页面中,可选择指定的表类型、存储节点、分片键、分片算法和分片数量等信息,,然后点击“确定”按钮。

  1. 确认表分布情况。

此时页面会弹出“新建成功”的提示信息。我们可通过点击对象列表中的“查看表分布”属性链接,确认分片表的分片信息。

通过 SphereEx-Console 创建分片表的操作结束。

使用自定义分片 #

通过配置分片策略类型和算法类名,实现自定义扩展。 CLASS_BASED 允许向算法类内传入额外的自定义属性,传入的属性可以通过属性名为 propsjava.util.Properties 类实例取出。 参考 Git 的 org.apache.shardingsphere.example.extension.sharding.algortihm.classbased.fixture.ClassBasedStandardShardingAlgorithmFixture

类型:CLASS_BASED

可配置属性:

属性名称数据类型说明
1strategyString分片策略类型,支持 STANDARD、COMPLEX 或 HINT(不区分大小写)
2algorithmClassNameString分片算法全限定名

配置示例 #

使用数据分片时,在 shardingAlgorithms 属性下配置对应的数据分片算法即可。

rules:
- !SHARDING
  tables:
    t_order: 
      actualDataNodes: ds_${0..1}.t_order_${0..1}
      tableStrategy: 
        standard:
          shardingColumn: order_id
          shardingAlgorithmName: t-order-inline
      keyGenerateStrategy:
        column: order_id
        keyGeneratorName: snowflake
    t_order_item:
      actualDataNodes: ds_${0..1}.t_order_item_${0..1}
      tableStrategy:
        standard:
          shardingColumn: order_id
          shardingAlgorithmName: t_order-item-inline
      keyGenerateStrategy:
        column: order_item_id
        keyGeneratorName: snowflake
    t_account:
      actualDataNodes: ds_${0..1}.t_account_${0..1}
      tableStrategy:
        standard:
          shardingAlgorithmName: t-account-inline
      keyGenerateStrategy:
        column: account_id
        keyGeneratorName: snowflake
  defaultShardingColumn: account_id
  bindingTables:
    - t_order,t_order_item
  broadcastTables:
    - t_address
  defaultDatabaseStrategy:
    standard:
      shardingColumn: user_id
      shardingAlgorithmName: database-inline
  defaultTableStrategy:
    none:
  
  shardingAlgorithms:
    database-inline:
      type: INLINE
      props:
        algorithm-expression: ds_${user_id % 2}
    t-order-inline:
      type: INLINE
      props:
        algorithm-expression: t_order_${order_id % 2}
    t_order-item-inline:
      type: INLINE
      props:
        algorithm-expression: t_order_item_${order_id % 2}
    t-account-inline:
      type: INLINE
      props:
        algorithm-expression: t_account_${account_id % 2}
  keyGenerators:
    snowflake:
      type: SNOWFLAKE

自动分片配置 #

SphereEx-DBPlusEngine 也能够支持自动化分片管理,当快到到达 range 算法时间上界时,自动向后建分片,提升用户使用体验。目前已支持 AUTO_INTERVAL 和 INTERVAL 算法,即不涉及数据移动的自动分片扩容。

以下是自动分片的简单示例,具体 DistSQL 使用方法请参考分片表

  • 标准分片规则
CREATE SHARDING TABLE RULE t_order_interval (
    DATANODES("${['ds_0','ds_1']}.t_order_interval_${['2023_06_01','2023_06_02','2023_06_03','2023_06_04','2023_06_05','2023_06_06','2023_06_07','2023_06_08','2023_06_09','2023_06_10','2023_06_11','2023_06_12','2023_06_13','2023_06_14','2023_06_15','2023_06_16','2023_06_17','2023_06_18','2023_06_19','2023_06_20','2023_06_21','2023_06_22','2023_06_23','2023_06_24','2023_06_25']}"),
    DATABASE_STRATEGY(TYPE="standard",SHARDING_COLUMN=user_id,SHARDING_ALGORITHM(TYPE(NAME="inline",PROPERTIES("algorithm-expression"="ds_${user_id % 2}")))),
    TABLE_STRATEGY(TYPE="standard",SHARDING_COLUMN=creation_date,SHARDING_ALGORITHM(TYPE(NAME="INTERVAL",PROPERTIES("datetime-pattern"="yyyy-MM-dd HH:mm:ss",
    "datetime-lower"="2023-06-01 00:00:00","datetime-upper"="2023-06-25 23:59:59","sharding-suffix-pattern"="yyyy_MM_dd","datetime-interval-amount"="1", "datetime-interval-unit"="DAYS")))),
    AUTO_RESHARDING_STRATEGY(
        MATCHING_ALGORITHM(TYPE(NAME="INTERVAL_UPPER_TIME_HIGH_WATERLINE",PROPERTIES("remaining-seconds-until-upper-time"=864000))),
        ACTION_ALGORITHM(TYPE(NAME="SCALE_SHARDING",PROPERTIES("operation-type"="ADD","amount"=20)))
    )
);

INTERVAL_UPPER_TIME_HIGH_WATERLINE:interval 算法中当前时间距离 upper-time 的时间少于配置的距离时间上界触发的时间,则自动向后分片指定时间

remaining-seconds-until-upper-time:距离 upper-time 还剩多少秒时,触发重分布,如上例子中是 864000 秒

operation-type:add,其他可选 subtract、multiply、divide

amount:与 operation-type 对应,比如增加或倍数,如上例子中是增加 2 片

  • 自动分片规则
CREATE SHARDING TABLE RULE t_order_auto_interval (
    STORAGE_UNITS(ds_0,ds_1),
    SHARDING_COLUMN=creation_date,TYPE(NAME="AUTO_INTERVAL",PROPERTIES("datetime-lower"="2023-06-01 00:00:00", "datetime-upper"="2023-06-25 23:59:59", "sharding-seconds"="86400")),
    AUTO_RESHARDING_STRATEGY(
        MATCHING_ALGORITHM(TYPE(NAME="INTERVAL_UPPER_TIME_HIGH_WATERLINE",PROPERTIES("remaining-seconds-until-upper-time"=864000))),
        ACTION_ALGORITHM(TYPE(NAME="SCALE_SHARDING",PROPERTIES("operation-type"="ADD","amount"=30)))
    )
);

FAQ #

  1. 使用分片插件后,DDL 可以通过 SphereEx-DBPlusEngine 来完成吗?

可以。通过 SphereEx-DBPlusEngine 可以像使用一个数据库一样来维护整个分布式架构,无需在数据库中逐一执行。

  1. 如果把目前在用的 MySQL 数据库迁移到分布式集群当中,是否有对应的方案?

有的。可以关注 SphereEx-DBPlusEngine 数据迁移和弹性扩容插件的能力,提供一站式的从集中式到分布式的数据迁移,同时还可以根据资源的使用情况合理的给存储节点做扩所容。