前文中介绍了分布式数据库中间件Mycat的一些特性,作为对比本文简要介绍Sharding-JDBC的一些特性以及分片的实现原理,进行对比分析以了解。
1、ShardingSphere介绍
ShardingSphere是一套开源的分布式数据库中间件解决方案,目前由Sharding-JDBC和Sharding-Proxy两款独立的产品组成,2020年4⽉16⽇正式成为 Apache 软件基⾦会的顶级项⽬。
- Sharding-JDBC:定位为轻量级Java框架,在Java的JDBC层提供的额外服务,支持任意实现JDBC规范的数据库。
- Sharding-Proxy:定位为透明化的数据库代理端,通过实现数据库二进制协议,对异构语言提供支持。目前提供MySQL和PostgreSQL协议。
ShardingSphere利用分布式场景下关系型数据库的计算和存储能力,提供标准化的数据分片、分布式事务和数据库治理功能,可以将任意数据库转换为分布式数据库,适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。
1.1 设计理念
ShardingSphere采用Database Plus的设计理念,致力于构建数据库上层的标准和生态,利用连接、增强和可插拔的方式在生态中补充数据库所缺失的能力。
Database Plus:一种分布式数据库系统的设计理念。旨在碎片化的异构数据库上层构建生态,在最大限度的复用数据库原生存算能力的前提下,进一步提供面向全局的扩展和叠加计算能力(例如:数据分片、数据加密等)。使应用和数据库间的交互面向Database Plus构建的标准,从而屏蔽数据库碎片化对上层业务带来的差异化影响。
1)连接:打造数据库上层标准
通过对数据库协议、SQL方言以及数据库存储的灵活适配,快速构建多模异构数据库上层的标准,同时通过内置DistSQL为应用提供标准化的连接方式。
2)增强:数据库计算增强引擎
在原生数据库基础能力之上,提供分布式及流量增强方面的能力。前者可突破底层数据库在计算与存储上的瓶颈,后者通过对流量的变形、重定向、治理、鉴权及分析能力提供更为丰富的数据应用增强能力。
3)可插拔:构建数据库功能生态
ShardingSphere的可插拔架构划分为3层,它们是:L1内核层、L2功能层、L3生态层
- L1内核层:是数据库基本能力的抽象,其所有组件均必须存在,但具体实现方式可通过可插拔的方式更换。主要包括查询优化器、分布式事务引擎、分布式执行引擎、权限引擎和调度引擎等。
- L2功能层:用于提供增量能力,其所有组件均是可选的,可以包含零至多个组件。组件之间完全隔离,互无感知,多组件可通过叠加的方式相互配合使用。主要包括数据分片、读写分离、数据库高可用、数据加密、影子库等。 用户自定义功能可完全面向Apache ShardingSphere定义的顶层接口进行定制化扩展,而无需改动内核代码。
- L3生态层:用于对接和融入现有数据库生态,包括数据库协议、SQL解析器和存储适配器,分别对应于Apache ShardingSphere以数据库协议提供服务的方式、SQL方言操作数据的方式以及对接存储节点的数据库类型。
1.2 产品规划
随着版本的不断迭代,ShardingSphere的功能也变得多元化起来:从最开始Sharding-JDBC 1.0版本只有数据分片,到2.0版本开始支持数据库治理,再到3.0版本Sharding-Proxy上线并支持分布式事务,再到4.0进入Apache基金会,再到如今5.0版本的可插拔式设计。其宗旨是构建数据库上层的标准和生态,建立统一标准和规范,完善数据库能力。
1.3 Sharding-JDBC
接下来重点介绍Sharding-JDBC框架,Sharding-JDBC定位为轻量级Java框架,在Java的JDBC层提供的额外服务。它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
- 适用于任何基于JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC;
- 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP等;
- 支持任意实现JDBC规范的数据库,目前支持MySQL,PostgreSQL,Oracle,SQLServer以及任何可使用JDBC访问的数据库。
1.3.1 Sharding-JDBC主要功能
Sharding-JDBC主要支持数据分片、分布式事务和数据库治理功能。
- 数据分片
- 分库分表:将数据分片进行垂直拆分和水平拆分
- 读写分离:根据SQL语义的分析,将读操作和写操作分别路由至主库与从库
- 分片策略:基于分片键和不同的分片算法实现分片
- 分布式主键:内置的分布式主键生成器,例如UUID、SNOWFLAKE
- 分布式事务
- 标准化的事务接口:begin/commit/rollback接口实现
- XA强一致性事务:AP、TM和RM模型保证分布式事务一致性
- 柔性事务:BASE事务模型实现事务的最终一致性
- 数据库治理
- 数据库网关:SQL方言自动翻译功能,将不同类型的数据库方言自动翻译为后端数据库所使用的方言
- 流量治理:计算节点的过载保护和数据节点限流
- 数据加密:数据加密功能,实现数据的合规化改造治理
- 可视化链路跟踪:多种监控性能和监控指标,实现监控仪表盘和应用链路跟踪
1.3.2 Sharding-JDBC处理流程
Sharding-JDBC定位为Java框架,对于开发人员只需要使用调用JDBC API访问数据库,只要正确使用DataSource、Connection、Statement 、ResultSet 等API接口,直接操作数据库即可,实现“创建DataSource->获取Connection->构建Statement->执行SQL语句->处理ResultSet”完整的流程。Sharding-JDBC就是将原来的DataSource、Connection等接口扩展成 ShardingDataSource、ShardingConnection,对外暴露的JDBC操作接口与JDBC规范中的接口完全一致。因此,Sharding-JDBC适用于任何基于JDBC的ORM框架,并完美兼容任何第三方的数据库连接池。
2、Sharding-JDBC核心原理
2.1 分片概念
数据分片通常将原本一张数据量很大的表根据分片规则和分片键拆分为表结构完全一样的小数据量的表,如下图所示,每张表只存储大表中的一部分数据。当SQL执行时会通过不同的分片策略,访问不同的库和分片中的数据。
2.1.1 表概念
- 逻辑表:相同结构的水平拆分数据库(表)的逻辑名称,是SQL中表的逻辑标识。比如图中的t_prod,拆分后数据库中已经不存在这张表,取而代之的是t_prod_n这些表,t_prod就称为这些拆分表的逻辑表。
- 真实表:数据库中真实存在的物理表,比如图中的t_prod_0…t_prod_3
- 数据节点:分库分表后不可再分割的最小的数据单元,由数据源名称和数据表组成,比如t_prod_db_1.t_prod_0就表示一个数据节点
- 绑定表:指代分片规则一致的一组分片表,比如t_prod和t_order均按照分片键id进行分片,则t_prod和t_order为绑定表。使用绑定表进行多表关联查询时,必须使用分片键进行关联,否则会出现笛卡尔积关联或跨库关联,从而影响查询效率。
- 广播表:指所有的分片数据源中都存在的表,表结构及其数据在每个数据库中均完全一致。适用于数据量不大且需要与海量数据的表进行关联查询的场景,如字典表、参数表。
- 单表:指所有的分片数据源中仅唯一存在的表。适用于数据量不大且无需分片的表。
2.1.2 分片算法
Sharding-JDBC对分片键基于一定的分片规则实现数据分片,从执行SQL的角度来看分片算法理解为一种路由机制。Sharding-JDBC支持以下内置的分片算法:
- 精确分片算法(PreciseShardingAlgorithm):用于单个字段作为分片键,SQL中有“=”和“IN”等条件的分片,需要在标准分片策略(StandardShardingStrategy)下使用。
- 范围分片算法(RangeShardingAlgorithm):范围分片算法用于单个字段作为分片键,SQL 中有“BETWEEN AND、>、<、>=、<=”下使用。
- 复合分片算法(ComplexKeysShardingAlgorithm):用于多个字段作为分片键的分片操作,同时获取到多个分片健的值,根据多个字段处理业务逻辑。需要在复合分片策略(ComplexShardingStrategy)下使用。
- Hint分片算法(HintShardingAlgorithm):上边的算法中我们都是解析语句提取分片键,并设置分片策略进行分片。但有些时候我们并没有使用任何的分片键和分片策略,可还想将SQL路由到目标数据库和表,就需要通过手动干预指定SQL的目标数据库和表信息,这也叫强制路由。
2.1.3 分片策略
分片策略是一个抽象的概念,实际的分片操作是由分片键+分片算法实现的。
- 标准分片策略:适用于单分片键,此策略支持PreciseShardingAlgorithm和RangeShardingAlgorithm 两个分片算法。
- 复合分片策略:支持多分片键,同样支持对SQL语句中的“=、>、<、>=、<=、IN、BETWEEN AND”的分片操作。
- 行表达式分片策略:支持对 SQL语句中的“=、IN”的分片操作,但只支持单分片键。这种策略通常用于简单的分片,不需要自定义分片算法,可以直接在配置文件中写规则。
- Hint分片策略:对应Hint分片算法,通过指定分片健而非从 SQL中提取分片健的方式进行分片的策略。
2.2 数据分片原理
Sharding-JDBC数据分片的原理如图所示,分为“SQL解析->执⾏器优化->SQL路由->SQL改写->SQL执⾏->结果归并”过程。
2.2.1 SQL解析
SQL解析分为词法解析和语法解析。先通过词法解析器将SQL拆分为一个个不可再分的单词,再使用语法解析器对SQL进行理解,并最终提炼出解析上下文。解析上下文包括表、选择项、排序项、分组项、聚合函数、分页信息、查询条件以及可能需要修改的占位符的标记。
SELECT id, name FROM t_user WHERE status = 'ACTIVE' AND age > 18
以上SQL语句,解析之后抽象的语法树如下:
通过对抽象语法树遍历,提炼出分片所需的上下文,上下文包含查询字段信息(Field)、表信息(Table)、查询条件(Condition)、排序信息(Order By)、分组信息(Group By)以及分页信息(Limit)等,并标记出 SQL中有可能需要改写的位置。
2.2.2 执行器优化
由Federation执行引擎(开发中)提供支持,对关联查询、子查询等复杂查询进行优化,同时支持跨多个数据库实例的分布式查询,内部使用关系代数优化查询计划,通过最优计划查询出结果。
2.2.3 SQL路由
SQL路由根据解析上下文匹配数据库和表的分片策略,并生成路由路径。对于携带分片键的SQL,根据分片键的不同可以划分为单片路由(分片键的操作符是等号)、多片路由(分片键的操作符是IN)和范围路由(分片键的操作符是BETWEEN)。简单点理解就是可以根据配置的分片策略计算出SQ该在哪个库的哪个表中执行,而SQL路由又根据有无分片健区分出分片路由和广播路由。
1)分片路由
用于根据分片键进行路由的场景,又细分为直接路由、标准路由和笛卡尔积路由这3种类型。
- 直接路由:满足直接路由的条件相对苛刻,它需要通过Hint(使用HintAPI直接指定路由至库表)方式分片,并且是只分库不分表的前提下,则可以避免SQL解析和之后的结果归并。
- 标准路由:当分片运算符是等于号时,路由结果将落入单库(表);当分片运算符是BETWEEN或IN时,则路由结果不一定落入唯一的库(表),因此一条逻辑SQL最终可能被拆分为多条用于执行的真实SQL。
- 笛卡尔积路由:它无法根据绑定表的关系定位分片规则,因此非绑定表之间的关联查询需要拆解为笛卡尔积组合执行。
2)广播路由:对于不携带分片键的SQL,则采取广播路由的方式。根据SQL类型又可以划分为全库表路由、全库路由、全实例路由、单播路由和阻断路由这5种类型。
- 全库表路由:全库表路由用于处理对数据库中与其逻辑表相关的所有真实表的操作,主要包括不带分片键的DQL和DML以及DDL等
- 全库路由:全库路由用于处理对数据库的操作,包括用于库设置的SET类型的数据库管理命令,以及TCL这样的事务控制语句。
- 全实例路由:全实例路由用于DCL操作,授权语句针对的是数据库的实例。
- 单播路由:单播路由用于获取某一真实表信息的场景,它仅需要从任意库中的任意真实表中获取数据即可。
- 阻断路由:阻断路由用于屏蔽 SQL 对数据库的操作,比如use database
2.2.4 SQL改写
SQL改写是将SQL改写为在真实数据库中可以正确执行的语句。SQL改写分为正确性改写和优化改写。
- 正确性改写:在包含分表的场景中,需要将分表配置中的逻辑表名称改写为路由之后所获取的真实表名称。另外还包括包括补列和分页信息修正等内容。
- 优化改写:在不影响查询正确性的情况下,对性能进行提升的有效手段。分为单节点优化和流式归并优化
2.2.5 SQL执行
SQL执行引擎采用一套自动化的执行引擎,负责将路由和改写完成之后的真实SQL安全且高效发送到底层数据源执行。
SQL执行引擎分为准备阶段和执行阶段:
1)准备阶段
准备阶段用于准备执行的数据,分为结果集分组和执行单元创建两个步骤。
- 结果集分组:将SQL的路由结果按照数据源的名称进行分组
- 通过上一步骤获得的路由分组结果创建执行的单元
2)执行阶段
执行阶段用于真正的执行SQL,它分为分组执行和归并结果集生成两个步骤
- 分组执行将准备执行阶段生成的执行单元分组下发至底层并发执行引擎,并针对执行过程中的每个关键步骤发送事件。
- 归并结果集生成通过在执行准备阶段的获取的连接模式,生成内存归并结果集或流式归并结果集,并将其传递至结果归并引擎,以进行下一步的工作
2.2.6 SQL结果归并
SQL结果归并是将多个执行结果集归并以便于通过统一的JDBC接口输出。结果归并从功能上分为遍历、排序、分组、分页和聚合5种类型;从结构划分,可分为流式归并、内存归并和装饰者归并。
- 遍历归并:将多个数据结果集合并为一个单向链表,在遍历完成链表中当前数据结果集之后,将链表元素后移一位,继续遍历下一个数据结果集即可
- 排序归并:每个数据结果集自身是有序的,因此只需要将数据结果集当前游标指向的数据值进行排序即可,不需要对所有的结果集进行全量的排序,节省了内存的消耗
- 分组归并:分为流式分组归并和内存分组归并, 流式分组归并要求SQL的排序项与分组项的字段以及排序类型(ASC或DESC)必须保持一致。如果不一致,需要将所有的结果集数据加载至内存中进行分组和聚合。
- 聚合归并:使用聚合函数进行比较、累加和求平均值
- 分页归并:通过装饰者模式来增加对数据结果集进行分页的能力,将无需获取的数据过滤掉。
3、Sharding-JDBC和Mycat对比
Mycat和ShardingSphere(Sharding-JDBC)都是非常流行的开源分布式数据库中间件,各自具有一些独特的功能,也有很多企业成功应用的案例。二者之间的特性对比如下表所示:
特性 |
Mycat |
Sharding-JDBC |
开发语言 |
Java |
Java |
开源协议 |
GPL-2.0/GPL-3.0 |
Apache-2.0 |
数据库 |
多种 |
多种 |
连接数 |
低 |
高 |
应用语言 |
任意 |
Java |
代码入侵 |
无 |
需修改代码 |
性能 |
损耗略高 |
损耗低 |
无中心化 |
无 |
是 |
管理控制台 |
Mycat-web |
ShardingUI |
分库分表 |
单库多表/多库单表 |
支持 |
多租户方案 |
支持 |
无 |
读写分离 |
支持 |
支持 |
分片策略定制化 |
支持 |
支持 |
分布式主键 |
支持 |
支持 |
标准化事务接口 |
支持 |
支持 |
XA强一致性事务 |
支持 |
支持 |
柔性事务 |
无 |
支持 |
配置动态化 |
开发中 |
支持 |
编排治理 |
开发中 |
支持 |
数据脱敏 |
无 |
支持 |
可视化链路跟踪 |
无 |
支持 |
多节点操作 |
支持 |
支持 |
跨库关联 |
跨库2表JOIN |
无 |
IP白名单 |
支持 |
无 |
SQL黑名单 |
支持 |
无 |
存储过程 |
支持 |
无 |
Mycat是一个分布式数据库中间件,对前端来说相当于一个数据库代理。ShardingSphere定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。目前来看接入Apache基金会的ShardingSphere体系更加完善,社区更加活跃。
参考资料:
- 官网,https://shardingsphere.apache.org/document/current/cn/overview/
- https://blog.csdn.net/agonie201218/article/details/125164699
- https://blog.csdn.net/horses/article/details/106086208
- 分布式数据库中间件Mycat介绍