三、Sharding-JDBC 执行原理

Sharding-JDBC 执行原理

本文通过 黑马 sharding-jdbc 视频教程 摘录

1、基本概念

1.1 逻辑表

  水平拆分的数据包的总称。例如:订单数据表根据主键尾数拆分为 10 张表,分别是 t_order_0、t_order_1 到 t_order_9,他们的逻辑表名 为 t_order。

# 指定t_order表的数据分布情况配置数据节点
spring.shardingsphere.sharding.tables.t_order.actual-data-nodes = m1.t_order_$->{1..2}
    /**
     * 根据id查询订单
     * @param orderIds id集合
     * @return
     */
    @Select("<script>" +
            "select * from t_order o " +
            "where o.order_id in " +
            "<foreach collection = 'orderIds' open = '(' separator = ',' close = ')' item = 'id'> " +
            "#{id}" +
            "</foreach>" +
            "</script>")
    List<Map> selectOrderByIds(@Param("orderIds") List<Long> orderIds);

  如以上的 t_order 为逻辑表。

1.2 真实表

  在分片的数据库中真实存在的物理表。即上个示例中的 t_order_0 到 t_order_9。

1.3 数据节点

  数据分片的最小物理单元。由数据源名称和数据表组成,例如:ds_0.t_order_0。

1.4 绑定表

  指分片规则一致的主表和子表。例如:t_order 表中 t_order_item 表,均按照 order_id 分片,绑定表之间的分区键完全相同,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询料率将大大提升。举例说明,如果 SQL 为:

SELECT i.* FROM t_order o JOIN t_order_item 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 JOIN t_order_item_0 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 ON o.order_id WHERE o.order_id in (10, 11)

SELECT i.* FROM t_order_1 JOIN t_order_item_0 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 ON o.order_id WHERE o.order_id in (10, 11)

  在配置绑定表关系后,路由的 SQL 应该为 2 条,比如,绑定后,查询 t_order_0 时,则必然相关数据一定在 t_order_item_0 中:

SELECT i.* FROM t_order_0 JOIN t_order_item_0 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 ON o.order_id WHERE o.order_id in (10, 11)
1.5 广播表

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

1.6 分片键

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

1.7 分片算法

  通过分片算法将数据分片,支持通过 =、BETWEEN 和 IN 分片。分片算法需要应用方开发者自行实现,可实现的灵活度非常高。包括:精确分片算法、范围分片算法,符合分片算法等。例如:where order_id = ? 将采用精确分片算法,where order_id in (?, ? ,?) 将采用精确分片算法,where order_id BETWEEN ? and ? 将采用范围分片算法,符合分片算法用于分片有多个符合情况。

1.8 分片策略

  包含分片键和分片算法,由于分片算法的独立性,将其独立抽离。真正可用于分片操作的分片键 + 分片算法,也就是分片策略。内置的分片策略大致可以分为尾数取模、哈希、范围、标签、时间等。由用户配置的分片策略则更加灵活,常用的使用行表达式配置分片策略,它采用 Groovy 表达式表示,如 t_user_${u_id % 8} 表示 t_user 表根据 u_id 模 8,而分为 8 张表,表名称为 t_user_0 到 t_user_7。

1.9 自增主键生成策略

  通过在客户端生成自增主键替代以数据库原生自增主键的方式,做到分布式主键无重复。

2、SQL 解析

  当 Sharding-JDBC 接受到一条 SQL 语句时,会陆续执行 SQL 解析 => 查询优化 => SQL 路由 => SQL 改写 => SQL 执行 => 结果归并,最终返回执行结果。

在这里插入图片描述
  SQL 解析过程分为词法解析语法解析。词法解析器用于将 SQL 拆解为不可再分的原子符号,成为 Token。并根据不同数据库方言所提供的字典,将其归类为关键字,表达式,字面量和操作等。再使用语法解析器将 SQL 转换为抽象语法树。

  例如,以下 SQL:

SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18

  解析之后的抽象语法树见下图:
在这里插入图片描述
  为了便于理解,抽象语法树中的关键字的 Token 用绿色表示,变量的 Token 用红色表示,灰色表示可以进一步拆分。

  最后,通过对抽象语法树的遍历去提取分片所需的上下文,并标记有可能需要 SQL 改写(后边介绍)的位置。供分片使用的解析上下文包含查询选择项(Select Items)、表信息(Table)、分片条件(Sharding Condition)、自增主键信息(Auto increment Primary Key)、排序信息(Order by)、分组信息(Group By)以及分页信息(Group By)以及分页信息(Limit、Rownum、Top)。

3、 SQL 路由

  SQL 路由就是把针对逻辑表的数据操作印射到数据节点操作的过程。

  根据解析上下文匹配数据库和表的分片策略,并生成路由路径。对于携带分片键的 SQL,根据分片键操作符不同可以划分为单片路由(分片键的操作符是等号)、多片路由(分片键的操作符是 IN)和范围路由(分片键的操作符是 BETWEEN),不携带分片键的 SQL 则采用广播路由。根据分片键进行路路由的场景可分为直接路由、标准路由、笛卡尔路由等。

3.1 标准路由

  标准路由是 Sharding-JDBC 最为推荐使用的分片方式,它的使用范围是不包含关联查询或仅包含绑定表之间的关联查询的 SQL。当分片运算符是等于号时,路由结果将落入单库(表),当分片运算符是 BETWEEN 或 IN 时,则路由结果不一定落入唯一的库(表),因此一条逻辑 SQL 最终可能被拆分为多条用于执行的真实 SQL。举例说明,如果按照 order_id 的奇数和偶数进行数据分片,一个单表查询的 SQL 如下:

SELECT * FROM t_order WHERE order_id IN (1, 2);

  那么路由的结果应为:

SELECT * FROM t_order_0 WHERE order_id IN (1, 2);
SELECT * FROM t_order_1 WHERE order_id IN (1, 2);

  绑定表的关联查询与单表查询复杂度和性能相当。举例说明,如果一个包含绑定表的关联查询的 SQL 如下:

SELECT * FROM t_order o JOIN t_order_item i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

  那么路由的结果应为:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

  可以看到, SQL 拆分的数目与单表是一致的。

3.2 笛卡尔路由

  笛卡尔路由是最复杂的情况,它无法根据绑定表的关系定位分片规则,因此非绑定表之间的关联查询需要拆解为笛卡尔积组合执行。如果上个示例中的 SQL 并未配置绑定表关系,那么路由的结果应为:

SELECT * FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

SELECT * FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

SELECT * FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

SELECT * FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id = i.order_id WHERE order_id IN (1, 2);

笛卡尔路由查询性能较低,需谨慎使用。

3.3 全库表路由

  对于不携带分片键的 SQL,则采取广播路由的方式。根据 SQL 类型又可以划分为全库表路由、全库路由、全实例路由、单播路由和阻断路由这 5 钟类型。其中全库表路由用于处理对数据库中其逻辑表相关的所有真实表的操作,主要包括不带分片键的 DQL(数据查询)和 DML(数据操纵),以及 DDL(数据定义)等。例如:

SELECT * FROM t_order WHERE good_prority IN (1, 10);

  则会遍历所有数据库中的所有表,逐一匹配逻辑表和真实表名,能匹配得上则执行。路由后成为:

SELECT * FROM t_order_0 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_1 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_2 WHERE good_prority IN (1, 10);
SELECT * FROM t_order_3 WHERE good_prority IN (1, 10);

4、 SQL 改写

  工程师面向逻辑表书写的 SQL,并不能够直接在真实的数据库汇总执行,SQL 改写用于将逻辑 SQL 改写为在真实数据库汇总可以正确执行的 SQL。

  如一个简单的例子,若逻辑 SQL 为:

SELECT order_id FROM t_order WHERE order_id = 1;

  假设该 SQL 配置分片键 order_id,并且 order_id = 1 的情况,将路由至分片表 1。那么改写之后的 SQL 应该为:

SELECT order_id FROM t_order_1 WHERE order_id = 1;

  再比如,Sharding-JDBC 需要在结果归并时获取相应数据,但该数据并未能通过查询的 SQL 返回。这种情况主要是针对 GROUP BY 和 ORDER BY。结果归并时,需要根据 GROUP BY 和 ORDER BY 的字段项进行分组和排序,但如果原始 SQL 的选择项中若并未包含分组项或排序项,则需要对原始 SQL 进行改写。先看一下原始 SQL 中带有结果归并所需信息的场景:

SELECT order_id, user_id FROM t_order ORDER BY user_id;

  由于使用 user_id 进行排序,在结果归并中需要能够获取到 user_id 的数据,而上面的 SQL 是能够获取到 user_id 数据的,因此无需补列。

  如果选择项中不包含结果归并时所需的列,则需要进行补列,如以下 SQL:

SELECT order_id FROM t_order ORDER BY user_id ;

  由于原始 SQL 中并不包含需要在结果归并中需要获取的 user_id,因此需要对 SQL 进行补列改写。补列之后的 SQL 是:

SELECT order_id, user_id AS ORDER BY_DERIVED_0 FROM t_order ORDER BY user_id;

5、 SQL 执行

  Sharding-JDBC 采用一套自动化的执行引擎,负责将路由和改写完成之后的真实 SQL 安全且高效发送奥底层数据源执行。它不是简单地将 SQL 通过 JDBC 直接发送到数据源执行;也并非直接将执行请求放入到线程池去并发执行。它更关注平衡数据源连接创建以及内存占用锁产生的消耗,以及最大限度地合理利用并发等问题。执行引擎的目标是自动化的平衡资源控制与执行效率,他能在以下两种模式自适应切换:

5.1内存限制模式

  使用此模式的前提是,Sharding-JDBC 对一次操作所耗费的数据库连接数量不做限制。如果执行的 SQL 需要对某数据库实例中的 200 张表做操作,则对每张表创建一个新的数据库连接,并通过多线程的方式并发处理,以达成执行效率最大化。

5.2 连接限制模式

  使用此模式的前提是,Sharding-JDBC 严格控制对一次操作所耗费的数据库连接数量。如果实际执行的 SQL 需要对某数据库实例中的 200 张表做操作,那么只会创建唯一的数据库连接,并对其 200 张表串行处理。如果一次操作中的分片散落在不同的数据库,仍然采用多线程处理对不同库的操作,但每个库的每次操作仍然只创建一个唯一的数据库连接。

  内存限制模式适用于 OLAP 操作,可以通过放宽对数据库连接的限制提升系统的吞吐量;连接限制模式适用于 OLTP 操作,OLTP 通常带有分片键,会路由到单一的分片,因此严格控制数据库连接,以保证在线系统数据库资源能够被更多的应用所使用,是明智的选择。

6、 结果归并

  将从各个数据节点获取的多数据结果集,组合成为一个结果集并正确的返回至请求客户端,称为结果归并。

  Sharding-JDBC 支持的结果归并从功能上可分为遍历排序分组分页聚合 5 种类型,它们是组合而非互斥的关系。

  归并引擎的整体结构划分如下图:
在这里插入图片描述
  结果归并从借故偶划分可分为流式归并内存归并装饰者归并。流式归并和内存归并时互斥的,装饰者归并可以在流式归并和内存归并之上做进一步的处理。

6.1 内存归并

  内存归并很容易理解,他是将所有分片结果集的数据都遍历并存储在内存中,再通过统一的分组、排序以及聚合等计算之后,再将其封装成为逐条访问的数据结果集返回。这种方式及其消耗内存,因此对服务器的内存要求很高。

6.2 流式归并

  流式归并是指每一次从数据库结果集中获取到的数据,都能够通过游标逐条火球的方式范湖正确的单条数据,它与数据库原生的返回结果集的方式最为契合。我们用的最多的就是这种方式。

  下边举例说明排序归并的过程,如下图是一个通过分数进行排序的示例图,它采用流式归并方式。图中展示了 3 张表返回的数据结果,每个数据结果集已经根据分数排序完毕,但是 3 个数据结果集之间是无序的。将 3 个数据结果集的当前游标指向的数据值进行排序,并放入优先队列,t_score_0 的第一个数据值最大,t_score_2 的第一个数据值次之,t_score_1 的第一个数据值最小,因此优先级队列根据 t_score_0,t_score_2 和 t_score_1 的方式排序队列。

在这里插入图片描述
  下图则展现了进行 next 调用的时候,排序归并是如何进行的。 通过图中我们可以看到,当进行第一次 next 调用时,排在队列首位的 t_score_0 将会被弹出队列,并且将当前游标指向的数据值(也就是 100)返回至查询客户端,并且将游标下移一位之后,重新放入优先级队列。 而优先级队列也会根据 t_score_0 的当前数据结果集指向游标的数据值(这里是 90)进行排序,根据当前数值,t_score_0 排列在队列的最后一位。 之前队列中排名第二的 t_score_2 的数据结果集则自动排在了队列首位。

  在进行第二次 next 时,只需要将目前排列在队列首位的 t_score_2 弹出队列,并且将其数据结果集游标指向的值返回至客户端,并下移游标,继续加入队列排队,以此类推。 当一个结果集中已经没有数据了,则无需再次加入队列。

在这里插入图片描述

  可以看到,对于每个数据结果集中的数据有序,而多数据结果集整体无序的情况下,ShardingSphere 无需将所有的数据都加载至内存即可排序。 它使用的是流式归并的方式,每次 next 仅获取唯一正确的一条数据,极大的节省了内存的消耗。

6.3 装饰者归并

  装饰者归并是对所有的结果集归并进行统一的功能增强,比如归并时需要聚合 SUM 前,在进行聚合计算前,都会通过内存归并或流式归并查询出结果集。因此,聚合归并是在之前介绍的归并类型之上追加的归并能力,即装饰者模式。

7、总结

  通过以上内容介绍,相信大家已经了解到 Sharding-JDBC 基础概念、核心功能以及执行原理。

  基本概念:逻辑表,真实表,数据节点,绑定表,广播表,分片键,分片算法,分片策略,主键生成策略

  核心功能:数据分片,读写分离

  SQL 解析 => 查询优化 => SQL 路由 => SQL 改写 => SQL 执行 => 结果归并

  接下来通过一个个 demo,来演示 Sharding-JDBC 实际使用方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值