美文网首页程序员专栏
学习Sharding-JDBC,这些基本概念你不搞懂?

学习Sharding-JDBC,这些基本概念你不搞懂?

作者: 享学课堂 | 来源:发表于2020-10-21 22:06 被阅读0次

    前言

    在了解Sharding-JDBC的执行原理前,需要了解以下概念:

    逻辑表

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

    真实表

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

    数据节点

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

    绑定表

    指分片规则一致的主表和子表。例如:torder 表和 torderitem 表,均按照 orderid 分片,绑定表之间的分区键完全相同,则此两张表互为绑定表关系。绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。

    举例说明,如果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);
    

    配置绑定表关系后,路由的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);
    

    广播表

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

    分片键

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

    分片算法

    通过分片算法将数据分片,支持通过 =****、>=*、*<=、>、<、****BETWEEN 和 IN 分片。分片算法需要应用方开发者自行实现**,可实现的灵活度非常高。

    目前提供4种分片算法。由于分片算法和业务实现紧密相关,因此并未提供内置****分片算法*,而是通过*分片****策略将各种场景提炼出来**,提供更高层级的抽象,并提供接口让应用开发者自行实现分片算法。

    • 精确分片算法

    对应 PreciseShardingAlgorithm,用于处理使用单一键作为分片键的 = 与 IN 进行分片的场景。需要配合 StandardShardingStrategy 使用。

    • 范围分片算法

    对应 RangeShardingAlgorithm,用于处理使用单一键作为分片键的 BETWEEN AND、>、<、>=、<=进行分片的场景。需要配合 StandardShardingStrategy 使用。

    • 复合分片算法

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

    • Hint分片算法

    对应 HintShardingAlgorithm,用于处理使用 Hint 行分片的场景。需要配合HintShardingStrategy 使用。

    分片策略

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

    • 标准分片策略

    对应 StandardShardingStrategy。提供对 SQL语句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。StandardShardingStrategy 只支持单分片键,提供 PreciseShardingAlgorithm 和 RangeShardingAlgorithm 两个分片算法。

    PreciseShardingAlgorithm 是必选的,用于处理 = 和 IN 的分片

    RangeShardingAlgorithm 是可选的,用于处理 BETWEEN AND, >, <, >=, <=分片,如果不配置RangeShardingAlgorithm,SQL 中的BETWEEN AND 将按照全库路由处理。

    • 复合分片策略

    对应 ComplexShardingStrategy。复合分片策略。提供对 SQL 语句中的 =, >, <, >=, <=, IN 和 BETWEEN AND 的分片操作支持。ComplexShardingStrategy 支持多分片键,由于多分片键之间的关系复杂,因此并未进行过多的封装,而是直接将分片键值组合以及分片操作符透传至分片算法,完全由应用开发者实现,提供最大的灵活度。

    • 行表达式分片策略

    对应 InlineShardingStrategy。使用Groovy的表达式,提供对 SQL 语句中的 = 和 IN 的分片操作支持,只支持单分片键。对于简单的分片算法,可以通过简单的配置使用,从而避免繁琐的 Java 代码开发,如: tuser$->{uid % 8} 表示tuser表根据uid模8,而分成8张表,表名称为 tuser0 到 tuser_7。

    • Hint分片策略

    对应 HintShardingStrategy。通过 Hint 指定分片值而非从 SQL 中提取分片值的方式进行分片的策略。

    • 不分片策略

    对应 NoneShardingStrategy。不分片的策略。

    自增 主键生成策略

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

    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)以及分页信息(Limit、Rownum、Top)。

    SQL路由

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

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

    根据分片键进行路由的场景可分为直接路由、标准路由、笛卡尔路由等。

    标准路由

    标准路由是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拆分的数目与单表是一致的。

    笛卡尔路由

    笛卡尔路由是最复杂的情况,它无法根据绑定表的关系定位分片规则,因此非绑定表之间的关联查询需要拆解为笛卡尔积组合执行

    如果上个示例中的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);
    

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

    全库表路由

    对于不携带分片键的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);
    

    SQL改写

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

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

    SELECT order_id FROM t_order WHERE order_id=1;
    

    假设该SQL配置分片键orderid,并且orderid=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;
    

    由于使用userid进行排序,在结果归并中需要能够获取到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;
    

    SQL执行

    Sharding-JDBC采用一套自动化的执行引擎,负责将路由和改写完成之后的真实SQL安全且高效发送到底层数据源执行。它不是简单地将SQL通过JDBC直接发送至数据源执行;也并非直接将执行请求放入线程池去并发执行

    结果归并

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

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

    归并引擎的整体结构划分如下图:

    结果归并从结构划分可分为流式归并、内存归并和装饰者归并。流式归并和内存归并是互斥的,装饰者归并可以在流式归并和内存归并之上做进一步的处理

    总结

    今天老顾介绍了Sharding-JDBC基础概念、核心功能以及执行原理

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

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

    后面的文章,老顾将介绍Sharding-jdbc的实战使用,谢谢!!!

    相关文章

      网友评论

        本文标题:学习Sharding-JDBC,这些基本概念你不搞懂?

        本文链接:https://www.haomeiwen.com/subject/zakymktx.html