Shardingsphere使用配置及内核剖析

作者 : 开心源码 本文共10370个字,预计阅读时间需要26分钟 发布时间: 2022-05-14 共246人阅读

导读

  • 本文主要通过源码分析Shardingsphere原理
  • 关键字Shardingsphere使用、Shardingsphere源码、Shardingsphere执行流程
  • 版本:Shardingsphere 4.1.1
  • Shardingsphere 配置

    • Yaml
    • Java Config
    • SpringBoot ?
    • Spring命名空间
  • Shardingsphere 功能

    • 数据分片 ?
    • 读写分离
    • 强制路由(可以归为数据分片得一种)
    • 数据加密
    • 分布式事务

总之,功能很强大!!!
当然,本篇并不会分析所有功能点,而是讲解最常用得数据分片配置用法以及原理

如何在 SpringBoot中配置数据分片策略 ?

项目中引入依赖:

        <dependency>            <groupId>org.apache.shardingsphere</groupId>            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>            <version>4.1.1</version>        </dependency>

配置中心(Apollo、Nacos)或者者本地项目中引入配置文件,增加如下配置:

spring.shardingsphere.datasource.names=ds0,ds1spring.shardingsphere.datasource.ds0.type= com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.ds0.driver-class-name= com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds0.url= jdbc:mysql://xxxxxx:3306/ds0spring.shardingsphere.datasource.ds0.username= xxxspring.shardingsphere.datasource.ds0.password= xxxspring.shardingsphere.datasource.ds1.type= com.zaxxer.hikari.HikariDataSourcespring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.jdbc.Driverspring.shardingsphere.datasource.ds1.url=jdbc:mysql://xxxxxx:3306/ds1spring.shardingsphere.datasource.ds1.username= xxxspring.shardingsphere.datasource.ds1.password= xxxspring.shardingsphere.sharding.tables.logical_table.database-strategy.inline.sharding-column = sharding_columnspring.shardingsphere.sharding.tables.logical_table.database-strategy.inline.algorithm-expression = ds$->{ShardingHash.shardingDBValue(sharding_column,2)}spring.shardingsphere.sharding.tables.logical_table.actual-data-nodes = ds$->{0..1}.logical_table_$->{0..3}spring.shardingsphere.sharding.tables.logical_table.table-strategy.inline.sharding-column = sharding_columnspring.shardingsphere.sharding.tables.logical_table.table-strategy.inline.algorithm-expression = logical_table_$->{ShardingHash.shardingTBValue(sharding_column,2,4)}

上述配置关键点:
① spring.shardingsphere.datasource.names=ds0,ds1 表示两个数据源(一般测试分片要至少配置两个)
② spring.shardingsphere.sharding.tables.logical_table.database-strategy.inline.sharding-column = sharding_column 表示配置得逻辑表为 logical_table (实际上就是平时SQL文件中DML语句对应得表);数据库得分片字段为 sharding_column
③ spring.shardingsphere.sharding.tables.logical_table.database-strategy.inline.algorithm-expression =ds$ ->{ShardingHash.shardingDBValue(sharding_column,2)}
表示数据分片的算法是内联,并且指定了分片表达式(通过Groovy来解析表达式,当然也有简单的表达式 例如:sharding_column %2)。 $->{} 是标准语法,也可以是 ${} (不推荐这么配置
④ spring.shardingsphere.sharding.tables.logical_table.actual-data-nodes = ds$->{0..1}.logical_table_$->{0..3} 表示一条sql 会被路由的真实数据节点总共可以有这么多。
例如:此处会有两个数据源:ds0,ds1; 4张表:logical_table_0 、logical_table_1、logical_table_2、logical_table_3, 那么组合起来就会存在8种路由节点(意味着sql中若不带分片键,会导致全表路由( 很严重 ),后面会分析)。
⑤ spring.shardingsphere.sharding.tables.logical_table.table-strategy.inline.sharding-column = sharding_column 表示 logical_table逻辑表对应的分表字段是 sharding_column
⑥ spring.shardingsphere.sharding.tables.logical_table.table-strategy.inline.algorithm-expression = logical_table_$->{ShardingHash.shardingTBValue(sharding_column,2,4)} 表示 分表对应的表达式(同上述分库
⑦ 以上策略需要根据自己项目中的情况,来选取库分片字段与表分片字段以及相应的分片算法(一般情况下,库表分片字段最好一致,尽量的减少分布式事务发生以及减少实际的路由节点)

到这里,项目中已经集成了 Shardingsphere了。 用法简单的是不是有点怀疑自己漏了少量配置(实际上配置方式就是这么简单~)。

Shardingsphere 如何做到分库分表的 ?
  • 配置加载过程
    首先因为我们引入得 spring boot starter得依赖,我们大致即可以猜到:Shardingsphere 一定也有一个 相似得自动装配类,此处是(SpringBootConfiguration),不清楚自动装配得可以参考一下作者之前得文章(一文读懂SpringBoot自动装配原理)。找到了入口,我们就直接看一下源码:
    Shardingsphere得入口配置类,其中有四点着重分析一下。
    ① 此处表明,自动装配在 DataSourceAutoConfiguration 这个自动装配类之前完成。也就是Shardingsphere创立得数据源就是全局得数据源,项目只需涉及到对数据库得任何操作都会经过ShardingDataSource得这一层解决(④中创立得)。正是基于此,为后面得数据分片以及少量扩展埋下基础。还有一点就是,假如我们项目中使用了Mybatis这个ORM框架的话,会发现Mybatis得starter启动配置类是在 DataSourceAutoConfiguration 装配之后再进行装配得,如图:Mybatis入口配置类
    那么此时Mybatis使用得数据源就是 Shardingsphere配置得 ShardingDataSource。
    ② 将之前配置得规则映射到此配置文件中,为创立数据源得过程提供配置信息。
    dataSourceMap 对象存放得是配置得所有数据源映射信息,为后面获取数据库连接以及数据分片提供基础能力。
    ④ 通过 ShardingDataSourceFactory 这个工厂类来创立 ShardingDataSource数据源,ShardingDataSource得内部结构,创立数据源得过程中,有两步很关键:
    ① 初始化路由装饰器(路由引擎,SPI得方式,客户可以扩展)、创立SQL改写上下文装饰器(改写引擎,同上)、创立结果解决引擎(归并引擎,用于对查询结果合并解决,同上)
    ② 创立运行时上下文(全局分片运行时上下文,用于保存分片所需得相关配置),这两处关键步骤后面会单独分析。
    因为我们目前演示得是基于分片得策略配置,所以只有 ShardingRuleCondition 才满足装配条件。
    而在创立数据源得同时,会将配置得规则解析成 ShardingRule,供后续得数据库操作提供分片核心能力。其中有一个重要得配置转换过程。会将分表规则、分库规则、分表算法、分库算法等都解析到对应得 ShardingRuleConfiguration 通用分片配置类中, 如图:ShardingRuleConfigurationYamlSwapper#swap(YamlShardingRuleConfiguration),根据我们前面得配置,通过第一个for循环得解析可以将我们配置得分库、分表策略、分库算法、分表算法解析到TableRuleConfiguration中,每一张表都会对应一个配置类:
        for (Entry<String, YamlTableRuleConfiguration> entry : yamlConfiguration.getTables().entrySet()) {            YamlTableRuleConfiguration tableRuleConfig = entry.getValue();            tableRuleConfig.setLogicTable(entry.getKey());            result.getTableRuleConfigs().add(tableRuleConfigurationYamlSwapper.swap(tableRuleConfig));        }

SpringBootConfiguration -> ShardingDataSourceFactory -> ShardingRule -> ShardingDataSource -> ShardingRuntimeContext

  • 分片运行时上下文创立过程
    ① 如上面所说,创立数据源得时候会在构造器中将运行时上下文ShardingRuntimeContext一同创立出来,ShardingRuntimeContext得构造器如下图:
    ShardingRuntimeContext得构造器,再来看一下类关系图 image.png,发现运行时上下文进行了笼统,分片运行时上下文继承了 MultipleDataSourcesRuntimeContext 多数据源运行时上下文,而多数据源运行时上下文又继承了 AbstractRuntimeContext 笼统上下文。而创立 ShardingRuntimeContext 分片运行时上下文得时候会同时将分片规则保存在笼统类中,AbstractRuntimeContext 笼统上下文得初始化
    其中有几步关键点:
    ① 缓存整个分片规则,为后续得分片操作提供依据
    ② 缓存数据库类型,用于后续执行得时候加载对应数据库的元数据
    ③ 创立执行引擎根据当前执行连接能否持有事务(根据我们目前得配置是没有使用得)来决定是异步执行还是同步执行,根据配置得 executor.size 参数决定创立多少个线程得线程池。 默认不配置得话,使用 cachepool配置了就使用固定线程数得线程池
    解析引擎,用于解析SQL为笼统语法树,解析过程分为词法解析和语法解析。从3.0之后解析会全面替换为 ANTLR

ShardingRuntimeContext-> MultipleDataSourcesRuntimeContext -> AbstractRuntimeContext-> ExecutorEngine-> SQLParserEngine

  • 分片解决过程
    当然了,前面那么多得创立初始化过程都是为了分片做准备,我们接着就来着重分析一下分片解决得过程,下面我们通过查询请求来一探数据分片得整个过程:
    Ⅰ. org.apache.ibatis.executor.BaseExecutor#query,当一个新鲜出炉的查询语句执行时,首先会经过Mybatis层(前面一系列的过程此处不重点分析,有兴趣的可以看一下作者之前分享的Mybatis流程图),而后调用 queryFromDatabase方法,此方法中,通过模板笼统方法org.apache.ibatis.executor.BaseExecutor#doQuery,来找到具体的查询实现(假如没有特殊配置,此处是SimpleExecutor),并将查询结果存入本地一级缓存中。而在org.apache.ibatis.executor.SimpleExecutor#doQuery中,org.apache.ibatis.executor.SimpleExecutor#doQuery,此处会创立一个 Preparestatement实例,而此实例就是ShardingPreparedStatement。到此,我们离Shardingsphere的内核又近了一步 org.apache.ibatis.executor.SimpleExecutor#prepareStatement
    Ⅱ. 顺着上述思路,我们继续Debug往下走。接着会经过Mybatis的预编译SQL解决器,而后调用PreparedStatement的execute方法 org.apache.ibatis.executor.statement.PreparedStatementHandler#query, 通过①中的分析的我们知道,此处的PreparedStatement是ShardingPreparedStatement,所以调用的是ShardingPreparedStatement的execute方法。
    Ⅲ. 接下来真正开始切入到Shardingsphere的执行逻辑中了。ShardingPreparedStatement#execute
    如图在execute方法中,① 首先清除本地 PreparedStatementExecutor 中缓存的sql相关信息(创立执行单元的时候会将sql相关信息缓存到本地) ② 而后执行prepare方法,此方法中有两个很关键的操作:执行路由策略和SQL改写策略(这两步是分片的核心,另外也都是可供使用者扩展的)。如图:BasePrepareEngine#prepare
    Ⅳ. 路由引擎:首先来看一下 executeRoute方法,BasePrepareEngine#executeRoute
    ① 获取 已经注册的RouteDecorator类实例(前面创立数据源的时候初始化的,当然使用者也可以通过SPI的方式扩展自己所需要的)过滤掉泛型是BaseRule类型的(ShardingRule是其子类,所以重新的时候覆写 getType方法时,肯定要是BaseRule类型的)
    ② 实例化路由装饰器
    ③ 调用模板方法 route,最终会调用到DataNodeRouter 的 executeRoute方法,如图方法: DataNodeRouter#executeRoute,此处又有两步很关键的操作:① 解析引擎: 通过 SQLParserEngine 解析SQL(并且此处默认是会将解析后的语句缓存起来,也就证明了前面会什么会先清除缓存),而后通过调用parse0方法解析SQL并缓存,如图:SQLParserEngine#parse0 ② 循环执行注册了的路由装饰器,目前内置的路由装饰器有 默认提供的路由装饰器,我们主要分析一下 分片路由装饰器 ShardingRouteDecorator,此处又有两个比较关键的步骤:ShardingRouteDecorator
    ① 获取分片条件:根据不同的语句创立不同的 条件解析引擎来构造分片条件(获取的分片条件用于在执行路由判断时决定使用哪种分片策略)
    ② 通过工厂创立出 ShardingRouteEngine 实例,一般情况下 会创立出来 ShardingStandardRoutingEngine(没有配置什么骚操作的情况下),而后调用 标准路由执行引擎的 路由方法
    Ⅴ. 到这里,终于要执行路由了!!!如图: ShardingStandardRoutingEngine#route
    ① 根据路由节点生成路由结果 RouteResult
    ② 获取数据节点:此处获取的就是真实的SQL路由情况(比方:ds0.table_0),首先判断能否使用直接路由(强制路由),若使用则走强制路由的分片算法去计算分片;而后再判断能否根据分片条件去路由,若有的话,则根据配置的分片算法(内联)根据分片值计算出来具体分到哪个库哪张表;若都没有的话,则直接走混合路由的解决逻辑。
    Ⅵ. 我们此处分析上述第二种情况,根据分片条件去执行分片。 根据分片条件路由
    ① 首先获取数据的分片路由值,再获取表的分片路由值,而后调用route0方法根据数据库分片路由值与表分片路由值去获取路由
    ② 路由数据源
    ③ 路由表
    最后封装成路由节点。
    Ⅶ. 回到 ShardingPreparedStatement中,调用initPreparedStatementExecutor()) 初始化 PreparedStatementExecutor实例 并将解析出来的执行上下文中的相关SQL语句组设置到缓存中(此处会获取到需要执行的SQL集合,主要是通过maxConnectionsSizePerQuery每次执行时最大连接数来判断sql执行单元应该分成几组,maxConnectionsSizePerQuery的值默认是1。则表示,假如真实的sql有10条,那么每组拆分10条,总共拆分成1组,此时会判断 maxConnectionsSizePerQuery 能否大于10,小于的话则会选择当前批次执行的是连接限制模式(只允许占用一个库的一个连接),相反则是内存限制模式,不会限制创立的连接数),而后调用执行器的执行方法:如图: org.apache.shardingsphere.shardingjdbc.executor.PreparedStatementExecutor#execute
    ① 获取sql执行回调类(真正操作数据库)
    ② 调用 executeCallback方法,此方法继承自父类AbstractStatementExecutor,直接来看一下父类中的方法:AbstractStatementExecutor的执行方法,SQL执行模板 SQLExecuteTemplate类通过委派其成员 ExecutorEngine 执行引擎来执行真正的操作。
    Ⅷ. 执行引擎对拆分的SQL执行单元执行解决,如图:ExecutorEngine 执行引擎的核心流程
    ① 并发执行(能否是并发执行通过 能否持有事务来判断的,例如 本地事务但是你修改为非自动提交事务,那么此时就是持有事务状态,则此时就是同步执行语句)
    ② 迭代出SQL执行组的第一个,其他的SQL异步执行
    ③ 同步执行第一个SQL执行组(方便与后面的执行组进行合并起来)
    ④ 通过其内置的线程池来异步执行SQL
    此时一条查询语句到这里就执行完了,接下来我们接着分析对查询结果进行解决的操作
    Ⅸ. 再回到Mybatis中,最后对查询的结果集进行解决( resultSetHandler.<E> handleResultSets(ps),此处是DefaultResultSetHandler 结果集解决器 ),如图:org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets,首先调用getFirstResultSet去获取第一个结果集: DefaultResultSetHandler#getFirstResultSet,此处的 Statement 实例是 ShardingPreparedStatement,所以①此处会调用其 getResultSet方法。
    Ⅹ. 结果归并:将查询返回的结果集进行合并解决,Shardingsphere 的归并引擎功能上划分:遍历归并、排序归并(SQL中存在ORDER BY语句)、分组归并(SQL中有GroupBy子句)、聚合归并(含有聚合函数)、分页归并(含有Limit关键字),归并引擎的详细详情请参阅:归并引擎,如图:image.png
    ① 获取所有Statement对应的结果集,此处是拿到真正数据源所对应的Statement实例,比方:我现在的数据源是 HikariDateSource,那么拿到的就是 HikariProxyPreparedStatement.如图:ShardingPreparedStatement#getResultSets
    执行合并逻辑:首先将结果集封装成流式查询结果对象StreamQueryResult,接着创立合并引擎 MergeEngine,而后调用合并引擎的合并方法:org.apache.shardingsphere.underlying.pluggble.merge.MergeEngine#merge
    ③ 实例化合并引擎解决器ResultProcessEngine
    ④ 调用MergeEntry的 process 方法,委派来进行合并逻辑。org.apache.shardingsphere.underlying.merge.MergeEntry#process
    ⑤ ⑥ 中,判断若是 ResultMergerEngine类型的合并引擎,则调用其merge方法执行真正的合并逻辑。如下图 ShardingResultMergerEngine 类图, 显然满足类型判断,则此处会调用ShardingResultMergerEngine#newInstance 方法来实例化真正用于合并数据流的引擎。 ShardingResultMergerEngine#newInstance,显然此处是查询语句,那么最终用于合并的引擎就是 ShardingDQLResultMerger,而后执行其merge方法。如图:org.apache.shardingsphere.sharding.merge.dql.ShardingDQLResultMerger#merge
    ⑦ 中判断sql中包含哪些关键字,而后创立对应的合并结果,假如条件都不满足,那么默认会使用 遍历流式归并方式合并数据。假设 我们此处SQL中带有 order by关键字,那么创立得合并结果对象就是OrderByStreamMergedResultOrderByStreamMergedResult构造器
    ⑧ 对创立出来的排序合并结果进行装饰操作(就是判断有没有别的关键字,例如:Limit,假如有就会创立LimitDecoratorMergedResult 装饰器对象,在之前的排序合并基础上又多一个 Limit功能),再回到 ShardingPreparedStatement中,会创立一个 ShardingResultSet对象设置到当前的成员变量currentResultSet中,并返回。 此时假如是批量的场景,返回的结果集中实际上已经包含了所有的结果集(前面存放在OrderByStreamMergedResult的 orderByValuesQueue 队列中),引用官方的一个图就是:排序归并流程,当调用合并结果的 next方法时会执行如图的流程:调用Next方法,最后流程又回到Mybatis 结果集解决上了,将结果返回给请求调用方。

写在最后:

  • 假如你素来没接触过Shardingsphere,建议先去理解一下,而后再结合作者本篇源码剖析,可能会对你更有帮助 Shardingsphere 中文官网
  • 忙了很久一段时间(比996还严重得那种?),好久没有升级了,发现文采都没有以前那么好了?,大伙凑合看吧
  1. ☛ 文章要是勘误或者者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码取得的知识,难免会有疏忽!
  2. 要是感觉文章对你有所帮助,不妨点个关注,或者者移驾看一下作者的其余文集,也都是干活多多哦,文章也在全力升级中。
  3. 著作权归作者所有。商业转载请联络作者取得受权,非商业转载请注明出处! ·
说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Shardingsphere使用配置及内核剖析

发表回复