目录
一、ShardingSphere 基础架构
Apache ShardingSphere 5.x 版本开始致力于可插拔架构,项目的功能组件能够灵活的以可插拔的方式进行扩展。 目前,数据分片、读写分离、数据加密、影子库压测等功能,以及对 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。 Apache ShardingSphere 目前已提供数十个 SPI(Service Provider Interface) 作为系统的扩展点,而且仍在不断增加中。架构图如下:
二、ShardingSphere 数据分片的原理
ShardingSphere 的数据分片流程统一为 SQL 解析 → 路由 → 改写 → 执行 → 归并 五阶段(Standard 内核流程),复杂查询则引入 Federation 引擎进行优化(逻辑优化 + 物理优化)
Standard 内核流程,也叫 Simple Push Down 下推流程,程由 SQL 解析 => SQL 绑定 => SQL 路由 => SQL 改写 => SQL 执行 => 结果归并
组成,主要用于处理标准分片场景下的 SQL 执行。
而 复杂查询流程 则使用SQL Federation 执行引擎流程,SQL Federation 执行引擎流程由 SQL 解析 => SQL 绑定 => 逻辑优化 => 物理优化 => 数据拉取 => 算子执行
组成,SQL Federation 执行引擎内部进行逻辑优化和物理优化,在优化执行阶段依赖 Standard 内核流程,对优化后的逻辑 SQL 进行路由、改写、执行和归并。
SQL 执行流程
-
SQL 解析:解析 SQL 语法树,提取分片键、条件等。
-
路由决策:
-
含分片键 → 精准路由到 1 个分片(如
user_id=101
→ds_1.order_1
) -
无分片键 → 广播路由到所有分片(如
SELECT * FROM order
)
-
-
SQL 改写:将逻辑表名替换为物理表名(
order
→order_1
)。 -
执行引擎:多线程并发执行物理 SQL。
-
结果归并:
-
流式归并:排序查询(
ORDER BY
) -
聚合归并:
SUM()
,MAX()
等聚合函数 -
分页归并:
LIMIT 10,5
跨库分页截取
-
三、 核心引擎详解
1. SQL 解析引擎
-
词法 & 语法解析:将 SQL 拆解为 Token(关键字/字面量/操作符),生成抽象语法树(AST),标记需改写位置。
-
解析器演进:
-
1.4.x
前:Druid 解析器(性能快) -
1.5.x
:自研半理解式解析器(兼容性提升) -
3.0.x+
:ANTLR 引擎(支持多方言 + AST 缓存)。
-
-
优化建议:优先使用
PreparedStatement
预编译降低 ANTLR 开销。
例如,以下 SQL:
SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18
解析之后的为抽象语法树见下图。
为了便于理解,抽象语法树中的关键字的 Token 用绿色表示,变量的 Token 用红色表示,灰色表示需要进一步拆分。
2. 路由引擎
根据解析上下文匹配数据库和表的分片策略,并生成路由路径。对于携带分片键的 SQL,根据分片键的不同可以划分为单片路由(分片键的操作符是等号)、多片路由(分片键的操作符是 IN)和范围路由(分片键的操作符是 BETWEEN)。不携带分片键的 SQL 则采用广播路由。
分片策略通常可以采用由数据库内置或由用户方配置。数据库内置的方案较为简单,内置的分片策略大致可分为尾数取模、哈希、范围、标签、时间等。由用户方配置的分片策略则更加灵活,可以根据使用方需求定制复合分片策略。
实际使用时,应尽量使用分片路由,明确路由策略。因为广播路由影响过大,不利于集群管理及扩展。
根据分片键操作符匹配策略,生成物理执行路径:
路由类型 | 触发条件 | 行为 | 示例 |
---|---|---|---|
单片路由 | 分片键 = 等值查询 | 精准定位单分片 | user_id = 101 → ds1.user_1 |
多片路由 | 分片键 IN 查询 | 定位多个分片 | order_id IN (1001,1002) |
范围路由 | 分片键 BETWEEN /> | 定位连续分片范围 | create_time > '2023-01-01' |
广播路由 | 无分片键 | 遍历所有库表 | |
├─ 全库表路由 | DQL/DML 无分片键 | 所有分片并行执行 | SELECT * FROM orders |
├─ 全实例路由 | DCL 语句 | 每个数据库实例执行一次 | CREATE USER ... |
└─ 阻断路由 | 虚拟操作(如 USE DB ) | 不执行实际 SQL | 136 |
3. SQL 改写引擎
用户面向逻辑库与逻辑表书写的 SQL,并不能够直接在真实的数据库中执行,SQL 改写用于将逻辑 SQL 改写为在真实数据库中可以正确执行的 SQL。 它包括正确性改写和优化改写两部分。
正确性改写
在包含分表的场景中,需要将分表配置中的逻辑表名称改写为路由之后所获取的真实表名称。仅分库则不需要表名称的改写。除此之外,还包括补列和分页信息修正等内容。
优化改写
优化改写的目的是在不影响查询正确性的情况下,对性能进行提升的有效手段。它分为单节点优化和流式归并优化。比如我们之前提到,在当前版本下,对一个库的多次查询,会通过UNION 合并成一个大的SQL,这也是一种优化改写。
将逻辑 SQL 适配物理分片环境:
-
正确性改写:
-
表名/索引名替换(
t_order
→t_order_1
) -
补列(如
ORDER BY
缺失列、自增主键填充) -
分页修正(
LIMIT 10,5
改写为LIMIT 0,15
并内存归并)。
-
-
优化改写:
-
单节点优化(路由到单分片时移除冗余改写)
-
流式归并提示(添加
/*streaming*/
注释)。
-
4. 执行引擎
ShardingSphere 采用一套自动化的执行引擎,负责将路由和改写完成之后的真实 SQL 安全且高效发送到底层数据源执行。 它不是简单地将 SQL 通过 JDBC 直接发送至数据源执行;也并非直接将执行请求放入线程池去并发执行。它更关注平衡数据源连接创建以及内存占用所产生的消耗,以及最大限度地合理利用并发等问题。 执行引擎的目标是自动化的平衡资源控制与执行效率。
如果一条 SQL 在经过 ShardingSphere 的分片后,需要操作某数据库实例下的 200 张表。 那么,是选择创建 200 个连接并行执行,还是选择创建一个连接串行执行呢?效率与资源控制又应该如何抉择呢?
针对上述场景,ShardingSphere 提供了一种解决思路。 它提出了连接模式(Connection Mode)的概念,将其划分为内存限制模式(MEMORY_STRICTLY)和连接限制模式(CONNECTION_STRICTLY)这两种类型。
内存限制模式
使用此模式的前提是,ShardingSphere 对一次操作所耗费的数据库连接数量不做限制。 如果实际执行的 SQL 需要对某数据库实例中的 200 张表做操作,则对每张表创建一个新的数据库连接,并通过多线程的方式并发处理,以达成执行效率最大化。 并且在 SQL 满足条件情况下,优先选择流式归并,以防止出现内存溢出或避免频繁垃圾回收情况。
连接限制模式
使用此模式的前提是,ShardingSphere 严格控制对一次操作所耗费的数据库连接数量。 如果实际执行的 SQL 需要对某数据库实例中的 200 张表做操作,那么只会创建唯一的数据库连接,并对其 200 张表串行处理。 如果一次操作中的分片散落在不同的数据库,仍然采用多线程处理对不同库的操作,但每个库的每次操作仍然只创建一个唯一的数据库连接。 这样即可以防止对一次请求对数据库连接占用过多所带来的问题。该模式始终选择内存归并。
内存限制模式适用于 OLAP 操作,可以通过放宽对数据库连接的限制提升系统吞吐量; 连接限制模式适用于 OLTP 操作,OLTP 通常带有分片键,会路由到单一的分片,因此严格控制数据库连接,以保证在线系统数据库资源能够被更多的应用所使用,是明智的选择。
平衡连接资源与执行效率,动态选择连接模式:
模式 | 触发条件 | 执行策略 | 适用场景 |
---|---|---|---|
内存限制模式 | 每库需执行 SQL 数 ≤ max.connections.per.query | 每分片独立连接 + 并行 | OLAP |
连接限制模式 | 每库需执行 SQL 数 > max.connections.per.query | 连接复用 + 串行读取 | OLTP 110 |
触发条件公式:
执行阶段:
-
准备阶段:原子性获取所有需用的数据库连接,避免死锁。
-
执行阶段:分组并发执行 SQL,按需流式/内存归并10。
5. 结果归并引擎
归并引擎的整体结构划分如下图
将从各个数据节点获取的多数据结果集,组合成为一个结果集并正确的返回至请求客户端,称为结果归并。
ShardingSphere 支持的结果归并从功能上分为遍历、排序、分组、分页和聚合 5 种类型,它们是组合而非互斥的关系。 从结构划分,可分为流式归并、内存归并和装饰者归并。流式归并和内存归并是互斥的,装饰者归并可以在流式归并和内存归并之上做进一步的处理。
-
流式归并:游标逐条归并(适用于
ORDER BY
排序查询),内存占用低610。 -
内存归并:全结果集加载至内存后归并(适用于聚合函数如
MAX
)。 -
特殊归并:
-
平均值归并 → 转化为
SUM
+COUNT
累加 -
分组归并 → 多分片结果合并后二次分组18。
-
参考文章