网站优化套餐,软件定制和开发,西安最好的网站建设公司,山东微道商网络技术有限公司Sharding-JDBC系列
1、Sharding-JDBC分库分表的基本使用
2、Sharding-JDBC分库分表之SpringBoot分片策略
3、Sharding-JDBC分库分表之SpringBoot主从配置
4、SpringBoot集成Sharding-JDBC-5.3.0分库分表
5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表
6、【… Sharding-JDBC系列
1、Sharding-JDBC分库分表的基本使用
2、Sharding-JDBC分库分表之SpringBoot分片策略
3、Sharding-JDBC分库分表之SpringBoot主从配置
4、SpringBoot集成Sharding-JDBC-5.3.0分库分表
5、SpringBoot集成Sharding-JDBC-5.3.0实现按月动态建表分表
6、【源码】Sharding-JDBC源码分析之JDBC
7、【源码】Sharding-JDBC源码分析之SPI机制
8、【源码】Sharding-JDBC源码分析之Yaml分片配置文件解析原理
9、【源码】Sharding-JDBC源码分析之Yaml分片配置原理一
10、【源码】Sharding-JDBC源码分析之Yaml分片配置原理二
11、【源码】Sharding-JDBC源码分析之Yaml分片配置转换原理
12、【源码】Sharding-JDBC源码分析之ShardingSphereDataSource的创建原理
13、【源码】Sharding-JDBC源码分析之ContextManager创建中mode分片配置信息的持久化存储的原理
14、【源码】Sharding-JDBC源码分析之ContextManager创建中ShardingSphereDatabase的创建原理
15、【源码】Sharding-JDBC源码分析之分片规则生成器DatabaseRuleBuilder实现规则配置到规则对象的生成原理
16、【源码】Sharding-JDBC源码分析之配置数据库定义的表的元数据解析原理
17、【源码】Sharding-JDBC源码分析之ShardingSphereConnection的创建原理
18、【源码】Sharding-JDBC源码分析之ShardingSpherePreparedStatement的创建原理
19、【源码】Sharding-JDBC源码分析之Sql解析的原理
20、【源码】Sharding-JDBC源码分析之SQL路由及SingleSQLRouter单表路由
21、【源码】Sharding-JDBC源码分析之SQL中分片键路由ShardingSQLRouter的原理
22、【源码】Sharding-JDBC源码分析之SQL中读写分离路由ReadwriteSplittingSQLRouter的原理
23、 【源码】Sharding-JDBC源码分析之SQL中读写分离动态策略、数据库发现规则及DatabaseDiscoverySQLRouter路由的原理
24、【源码】Sharding-JDBC源码分析之SQL中影子库ShadowSQLRouter路由的原理
前言
ShardingSphere 影子库是一个在数据库层面解决全链路在线压测问题的有效工具。影子库是实际中使用的数据库的完整数据拷贝用于接收测试数据以防止测试数据污染生产数据库。影子库应与正式的生产库保持相同的配置以确保测试结果的准确性。
在正式环境中进行全链路压测时使用影子库可以隔离测试数据可以模拟生产环境进行各种测试而不会对生产数据库造成任何影响。
本篇从源码的角度分析 ShardingSphere 中影子库路由的实现原理其对应的路由器对象为ShadowSQLRouter。
ShardingSpherePreparedStatement回顾
在【源码】Sharding-JDBC源码分析之SQL路由及SingleSQLRouter单表路由-CSDN博客中分析在执行SQL语句前会进行SQL路由创建RouteContext对象在RouteContext路由上下文对象中包含了SQL真正执行的数据源、逻辑表及真实表的映射。
SQL路由时循环执行配置的路由器进行路由上下文RouteContext对象的创建或装饰。如果配置了影子库将会在路由器集合中最后执行影子库路由器 ShadowSQLRouter。
ShadowSQLRouter
ShadowSQLRouter的源码如下
package org.apache.shardingsphere.shadow.route;/*** SQL影子库路由器*/
public final class ShadowSQLRouter implements SQLRouterShadowRule {Overridepublic RouteContext createRouteContext(final QueryContext queryContext, final ShardingSphereDatabase database,final ShadowRule rule, final ConfigurationProperties props, final ConnectionContext connectionContext) {// TODOreturn new RouteContext();}/*** 装饰路由上下文。根据 SQL 语句类型创建一个ShadowRouteEngine执行ShadowRouteEngine.route()方法* param routeContext 路由上下文* param queryContext 查询上下文* param database 数据库信息* param rule 影子库规则对象* param props 配置的属性* param connectionContext 连接上下文*/Overridepublic void decorateRouteContext(final RouteContext routeContext, final QueryContext queryContext, final ShardingSphereDatabase database,final ShadowRule rule, final ConfigurationProperties props, final ConnectionContext connectionContext) {ShadowRouteEngineFactory.newInstance(queryContext).route(routeContext, rule);}Overridepublic int getOrder() {return ShadowOrder.ORDER;}Overridepublic ClassShadowRule getTypeClass() {return ShadowRule.class;}
}
ShadowSQLRouter的源码很简单通过ShadowRouteEngineFactory的newInstance()获取一个ShadowRouteEngine对象执行ShadowRouteEngine的route()方法。
ShadowRouteEngineFactory
ShadowRouteEngineFactory的源码如下
package org.apache.shardingsphere.shadow.route.engine;/*** 影子路由引擎工厂*/
NoArgsConstructor(access AccessLevel.PRIVATE)
public final class ShadowRouteEngineFactory {/*** 创建影子路由引擎。不同的SQL语句使用不同的处理方式* param queryContext* return*/public static ShadowRouteEngine newInstance(final QueryContext queryContext) {SQLStatement sqlStatement queryContext.getSqlStatementContext().getSqlStatement();// 插入语句if (sqlStatement instanceof InsertStatement) {return createShadowInsertStatementRoutingEngine(queryContext);}// 删除语句if (sqlStatement instanceof DeleteStatement) {return createShadowDeleteStatementRoutingEngine(queryContext);}// 修改语句if (sqlStatement instanceof UpdateStatement) {return createShadowUpdateStatementRoutingEngine(queryContext);}// 选择语句if (sqlStatement instanceof SelectStatement) {return createShadowSelectStatementRoutingEngine(queryContext);}// 其他语句return createShadowNonMDLStatementRoutingEngine(queryContext);}private static ShadowRouteEngine createShadowNonMDLStatementRoutingEngine(final QueryContext queryContext) {return new ShadowNonDMLStatementRoutingEngine(queryContext.getSqlStatementContext());}private static ShadowRouteEngine createShadowSelectStatementRoutingEngine(final QueryContext queryContext) {return new ShadowSelectStatementRoutingEngine((SelectStatementContext) queryContext.getSqlStatementContext(), queryContext.getParameters());}private static ShadowRouteEngine createShadowUpdateStatementRoutingEngine(final QueryContext queryContext) {return new ShadowUpdateStatementRoutingEngine((UpdateStatementContext) queryContext.getSqlStatementContext(), queryContext.getParameters());}private static ShadowRouteEngine createShadowDeleteStatementRoutingEngine(final QueryContext queryContext) {return new ShadowDeleteStatementRoutingEngine((DeleteStatementContext) queryContext.getSqlStatementContext(), queryContext.getParameters());}/*** 传入插入语句的路由引擎* param queryContext* return*/private static ShadowRouteEngine createShadowInsertStatementRoutingEngine(final QueryContext queryContext) {return new ShadowInsertStatementRoutingEngine((InsertStatementContext) queryContext.getSqlStatementContext());}
}
在newInstance()方法中根据当前SQL语句的操作类型创建不同的ShadowRouteEngine影子路由引擎对象。如针对插入语句创建ShadowInsertStatementRoutingEngine路由引擎。
ShadowInsertStatementRoutingEngine
ShadowInsertStatementRoutingEngine的源码如下
package org.apache.shardingsphere.shadow.route.engine.dml;/*** 插入语句影子路由引擎*/
RequiredArgsConstructor
public final class ShadowInsertStatementRoutingEngine extends AbstractShadowDMLStatementRouteEngine {// 插入语句上下文private final InsertStatementContext insertStatementContext;/*** 插入语句中涉及的表* return*/Overrideprotected CollectionSimpleTableSegment getAllTables() {return insertStatementContext.getAllTables();}Overrideprotected ShadowOperationType getShadowOperationType() {return ShadowOperationType.INSERT;}/*** 获取插入语句中的注释段信息* return*/Overrideprotected OptionalCollectionString parseSQLComments() {CollectionString result new LinkedList();insertStatementContext.getSqlStatement().getCommentSegments().forEach(each - result.add(each.getText()));return result.isEmpty() ? Optional.empty() : Optional.of(result);}Overrideprotected IteratorOptionalShadowColumnCondition getShadowColumnConditionIterator(final String shadowColumn) {return new ShadowColumnConditionIterator(shadowColumn, parseColumnNames().iterator(), insertStatementContext.getInsertValueContexts());}/*** 解析插入语句中的参数名称* return*/private CollectionString parseColumnNames() {return insertStatementContext.getInsertColumnNames();}/*** 影子列条件迭代器*/private class ShadowColumnConditionIterator implements IteratorOptionalShadowColumnCondition {private int index;// 影子列private final String shadowColumn;// 插入语句中的列名迭代器private final IteratorString iterator;// 插入值上下文集合。每个插入值上下文中包含单条记录的参数信息private final ListInsertValueContext insertValueContexts;ShadowColumnConditionIterator(final String shadowColumn, final IteratorString iterator, final ListInsertValueContext insertValueContexts) {index 0;this.shadowColumn shadowColumn;this.iterator iterator;this.insertValueContexts insertValueContexts;}Overridepublic boolean hasNext() {return iterator.hasNext();}/*** 遍历下一个参数如果不是影子列返回空* return*/Overridepublic OptionalShadowColumnCondition next() {String columnName iterator.next();// 不是影子列返回空if (!shadowColumn.equals(columnName)) {index;return Optional.empty();}// 如果是影子列OptionalCollectionComparable? columnValues getColumnValues(insertValueContexts, index);index;return columnValues.map(each - new ShadowColumnCondition(getSingleTableName(), columnName, each));}/*** 获取列的值* param insertValueContexts* param columnIndex 当前投影列在列名集合中的下标* return*/private OptionalCollectionComparable? getColumnValues(final ListInsertValueContext insertValueContexts, final int columnIndex) {CollectionComparable? result new LinkedList();for (InsertValueContext each : insertValueContexts) {// 获取参数值或参数在sql语句中的下标Object valueObject each.getLiteralValue(columnIndex).orElseThrow(() - new UnsupportedShadowInsertValueException(columnIndex));if (valueObject instanceof Comparable?) {result.add((Comparable?) valueObject);} else {return Optional.empty();}}return result.isEmpty() ? Optional.empty() : Optional.of(result);}}
}
ShadowInsertStatementRoutingEngine继承抽象类AbstractShadowDMLStatementRouteEngine其实现的核心逻辑都在父类AbstractShadowDMLStatementRouteEngine中具体SQL语句的实现类核心功能在于获取对应影子列的值的获取。
在ShadowInsertStatementRoutingEngine中主要功能如下
1获取插入SQL语句中的所有表部分
2获取插入SQL语句中的注解
3获取投影列的条件迭代器 迭代器提供了获取插入语句中配置了投影的列及对应列准备插入的值。在抽象父类中通过列及对应的值结合配置的算法确认是否要将数据源替换为对应的投影数据源 AbstractShadowDMLStatementRouteEngine
AbstractShadowDMLStatementRouteEngine的源码如下
package org.apache.shardingsphere.shadow.route.engine.dml;/*** DML语句的影子路由引擎*/
Getter
public abstract class AbstractShadowDMLStatementRouteEngine implements ShadowRouteEngine {// 表昵称和表名的映射private final MapString, String tableAliasNameMappings new LinkedHashMap();/*** 影子库路由* param routeContext route context* param shadowRule shadow rule*/Overridepublic void route(final RouteContext routeContext, final ShadowRule shadowRule) {decorateRouteContext(routeContext, shadowRule, findShadowDataSourceMappings(shadowRule));}/*** 查找影子库数据源映射。key为生产数据源、value为影子数据源* param shadowRule* return*/private MapString, String findShadowDataSourceMappings(final ShadowRule shadowRule) {// 从SQL语句中的所有表查找配置影子规则的表CollectionString relatedShadowTables getRelatedShadowTables(getAllTables(), shadowRule);if (relatedShadowTables.isEmpty() isMatchDefaultShadowAlgorithm(shadowRule)) {return shadowRule.getAllShadowDataSourceMappings();}ShadowOperationType shadowOperationType getShadowOperationType();// 判断 sql 注释中是否有shadow的提示。如 影子算法为 SIMPLE_HINT且sql语句中添加了 /* SHARDINGSPHERE_HINT: SHADOWtrue */MapString, String result findBySQLComments(relatedShadowTables, shadowRule, shadowOperationType);if (!result.isEmpty()) {return result;}// 查找表中的是否存在满足条件的影子列如果存在则返回对应表的数据源映射return findByShadowColumn(relatedShadowTables, shadowRule, shadowOperationType);}/*** 结合配置的规则查找配置了影子规则的表* param simpleTableSegments* param shadowRule* return*/private CollectionString getRelatedShadowTables(final CollectionSimpleTableSegment simpleTableSegments, final ShadowRule shadowRule) {CollectionString tableNames new LinkedHashSet();// 遍历表部分解析出表名及表昵称映射for (SimpleTableSegment each : simpleTableSegments) {String tableName each.getTableName().getIdentifier().getValue();String alias each.getAlias().isPresent() ? each.getAlias().get() : tableName;tableNames.add(tableName);tableAliasNameMappings.put(alias, tableName);}// 从影子规则中获取相关的表return shadowRule.getRelatedShadowTables(tableNames);}/*** 是否匹配默认阴影算法* param shadowRule* return*/SuppressWarnings(unchecked)private boolean isMatchDefaultShadowAlgorithm(final ShadowRule shadowRule) {OptionalCollectionString sqlComments parseSQLComments();// 如果没有设置注释段返回falseif (!sqlComments.isPresent()) {return false;}// 获取默认影子算法OptionalShadowAlgorithm defaultShadowAlgorithm shadowRule.getDefaultShadowAlgorithm();if (defaultShadowAlgorithm.isPresent()) {ShadowAlgorithm shadowAlgorithm defaultShadowAlgorithm.get();// 是Hint的影子算法if (shadowAlgorithm instanceof HintShadowAlgorithm?) {// 创建影子确定条件影子操作类型为HINT_MATCHShadowDetermineCondition shadowDetermineCondition new ShadowDetermineCondition(, ShadowOperationType.HINT_MATCH);// 是否在hint影子算法中。如sql注释中是否添加了 shadow 的hint提示return HintShadowAlgorithmDeterminer.isShadow((HintShadowAlgorithmComparable?) shadowAlgorithm, shadowDetermineCondition.initSQLComments(sqlComments.get()), shadowRule);}}return false;}/*** 查找SQL的注释部分获取数据源信息。key为生产数据源、value为影子数据源* param relatedShadowTables sql语句中配置了影子规则的表* param shadowRule 影子规则对象* param shadowOperationType 当前sql操作的类型* return*/private MapString, String findBySQLComments(final CollectionString relatedShadowTables, final ShadowRule shadowRule, final ShadowOperationType shadowOperationType) {MapString, String result new LinkedHashMap();// 遍历影子表for (String each : relatedShadowTables) {// 判断sql的注释中是否包含了shadow信息如 /* SHARDINGSPHERE_HINT: SHADOWtrue */if (isContainsShadowInSQLComments(each, shadowRule, new ShadowDetermineCondition(each, shadowOperationType))) {// 获取影子数据源映射。key为生产数据源、value为影子数据源result.putAll(shadowRule.getRelatedShadowDataSourceMappings(each));return result;}}return result;}/*** 判断sql语句的注释段是否包含了 shadow 的注释* param tableName* param shadowRule* param shadowCondition* return*/private boolean isContainsShadowInSQLComments(final String tableName, final ShadowRule shadowRule, final ShadowDetermineCondition shadowCondition) {// 获取解析后的 SQL 注释判断注释中是否包含了 shadow 的信息return parseSQLComments().filter(each - isMatchAnyHintShadowAlgorithms(shadowRule.getRelatedHintShadowAlgorithms(tableName), shadowCondition.initSQLComments(each), shadowRule)).isPresent();}/*** 是否匹配任意的Hint影子算法* param shadowAlgorithms* param shadowCondition* param shadowRule* return*/private boolean isMatchAnyHintShadowAlgorithms(final CollectionHintShadowAlgorithmComparable? shadowAlgorithms, final ShadowDetermineCondition shadowCondition, final ShadowRule shadowRule) {// 遍历hint影子算法for (HintShadowAlgorithmComparable? each : shadowAlgorithms) {// 判断是否满足hint影子算法中的影子规则// 默认的hint影子算法为SimpleHintShadowAlgorithm配置的type为SIMPLE_HINT// 该算法解析传入的shadowCondition中的sqlComment是否存在 /* SHARDINGSPHERE_HINT: SHADOWtrue */ 的信息// 即判断对应的sql语句是否有 /* SHARDINGSPHERE_HINT: SHADOWtrue */ 相关注解信息if (HintShadowAlgorithmDeterminer.isShadow(each, shadowCondition, shadowRule)) {return true;}}return false;}/*** 通过影子列查找数据源映射key为生产数据源、value为影子数据源* param relatedShadowTables 相关影子表* param shadowRule 影子规则* param shadowOperationType 影子操作类型* return*/private MapString, String findByShadowColumn(final CollectionString relatedShadowTables, final ShadowRule shadowRule, final ShadowOperationType shadowOperationType) {MapString, String result new LinkedHashMap();// 遍历for (String each : relatedShadowTables) {// 获取相关影子表的影子列名称CollectionString relatedShadowColumnNames shadowRule.getRelatedShadowColumnNames(shadowOperationType, each);// 存在影子列 匹配任意列影子算法if (!relatedShadowColumnNames.isEmpty() isMatchAnyColumnShadowAlgorithms(each, relatedShadowColumnNames, shadowRule, shadowOperationType)) {// 返回对应表的影子数据源映射return shadowRule.getRelatedShadowDataSourceMappings(each);}}return result;}/*** 匹配任意列投影算法* param shadowTable 影子表* param shadowColumnNames 设置影子的列* param shadowRule* param shadowOperation SQL的操作类型* return*/private boolean isMatchAnyColumnShadowAlgorithms(final String shadowTable, final CollectionString shadowColumnNames, final ShadowRule shadowRule, final ShadowOperationType shadowOperation) {// 遍历投影的列名for (String each : shadowColumnNames) {// 匹配任意列影子算法if (isMatchAnyColumnShadowAlgorithms(shadowTable, each, shadowOperation, shadowRule)) {return true;}}return false;}/*** 匹配任意列影子算法* param shadowTable 影子表* param shadowColumn 影子列表中设置了影子的列* param shadowOperationType* param shadowRule* return*/private boolean isMatchAnyColumnShadowAlgorithms(final String shadowTable, final String shadowColumn, final ShadowOperationType shadowOperationType, final ShadowRule shadowRule) {// 从影子规则中获取 shadowColumn 列的影子算法CollectionColumnShadowAlgorithmComparable? columnShadowAlgorithms shadowRule.getRelatedColumnShadowAlgorithms(shadowOperationType, shadowTable, shadowColumn);if (columnShadowAlgorithms.isEmpty()) {return false;}// 获取影子列的条件迭代器IteratorOptionalShadowColumnCondition iterator getShadowColumnConditionIterator(shadowColumn);ShadowDetermineCondition shadowDetermineCondition;// 遍历迭代器while (iterator.hasNext()) {// 获取SQL中下一个列的条件信息OptionalShadowColumnCondition next iterator.next();// 如果找到影子列if (next.isPresent()) {for (ColumnShadowAlgorithmComparable? each : columnShadowAlgorithms) {// 创建一个影子确定条件对象shadowDetermineCondition new ShadowDetermineCondition(shadowTable, shadowOperationType);// 通过决策器进行影子判断if (ColumnShadowAlgorithmDeterminer.isShadow(each, shadowDetermineCondition.initShadowColumnCondition(next.get()))) {return true;}}}}return false;}/*** 获取sql语句中所有的表* return*/protected abstract CollectionSimpleTableSegment getAllTables();/*** 获取影子库映射类型即当前sql语句的类型。如INSERT、UPDATE等* return*/protected abstract ShadowOperationType getShadowOperationType();/*** 解析SQL语句的注释信息* return*/protected abstract OptionalCollectionString parseSQLComments();/*** 获取投影列的条件迭代器* param shadowColumn* return*/protected abstract IteratorOptionalShadowColumnCondition getShadowColumnConditionIterator(String shadowColumn);/*** 获取第一个表名* return*/protected String getSingleTableName() {return tableAliasNameMappings.entrySet().iterator().next().getValue();}
}
在ShadowSQLRouter的decorateRouteContext()方法中通过ShadowRouteEngineFactory的newInstance()获取一个ShadowRouteEngine对象执行ShadowRouteEngine的route()方法。即执行AbstractShadowDMLStatementRouteEngine的route()方法。
route()方法执行如下
1执行findShadowDataSourceMappings()方法查找影子库数据源映射。key为生产数据源、value为影子数据源 1.1从SQL语句中的所有表查找配置影子规则的表 1.2如果没有满足的表 投影规则配置了默认投影算法则返回配置的所有影子数据源 1.3否则 1.3.1查找SQL的注释部分获取数据源信息。key为生产数据源、value为影子数据源如果找到返回结果否则往下继续执行 1.3.2查找表中的是否存在满足条件的影子列如果存在则返回对应表的数据源映射 2执行父类ShadowRouteEngine的decorateRouteContext()装饰路由上下文 遍历当前路由上下文中的路由单元根据路由单元中的实际数据源执行如下 2.1如果实际数据源配置了影子规则则继续执行 2.2从1中查找对应的实际数据源在当前SQL语句中是否满足了影子的条件如果存在就满足不存在就不满足存在则替换为影子数据源 影子库配置示例
rules:- !SHADOWdataSources:ds1: # 投影组的逻辑数据源的名称productionDataSourceName: dsshadowDataSourceName: shadow_dstables:t_order:dataSourceNames:- ds1 # 以上 dataSources下的dsshadowAlgorithmNames:- user_id_insert_match_algorithm- sql_hint_algorithmshadowAlgorithms:user_id_insert_match_algorithm:type: REGEX_MATCHprops:operation: insertcolumn: user_idregex: [1]sql_hint_algorithm:type: SQL_HINT
小结
以上为本篇分析的全部内容以下做一个小结
1ShardingSphere 影子库是一个在数据库层面解决全链路在线压测问题的有效工具影子库用于接收测试数据以防止测试数据污染生产数据库
2影子库规则对应的路由器对象为ShadowSQLRouter在ShardingSpherePreparedStatement执行SQL语句进行SQL路由创建RouteContext路由上下文时最后执行的路由器
3影子库路由器中通过ShadowRouteEngineFactory的newInstance()创建影子库路由引擎 不同的SQL操作语句创建不同的路由引擎如插入语句创建ShadowInsertStatementRoutingEngine 4ShadowInsertStatementRoutingEngine插入语句的路由引擎继承于抽象类AbstractShadowDMLStatementRouteEngine。父类负责整体逻辑允许针对不同SQL操作语句的实现类路由引擎提供对应影子库判断信息 4.1在父类AbstractShadowDMLStatementRouteEngine的route()路由方法中查找SQL语句中满足影子库规则的数据源映射 4.1.1从SQL语句中的所有表查找配置影子规则的表。如果没有满足的表 投影规则配置了默认投影算法则返回配置的所有影子数据源 4.1.2查找SQL的注释部分获取数据源信息。key为生产数据源、value为影子数据源如果找到返回结果 4.1.3查找表中的是否存在满足条件的影子列如果存在则返回对应表的数据源映射 4.1.3.1在ShadowInsertStatementRoutingEngine中解析SQL的插入表及列判断对应表的列是否配置了影子库路由规则如果有配置返回列及对应列的插入值 4.1.3.2在父类AbstractShadowDMLStatementRouteEngine中获取子类提供的设置了影子列及值执行影子库算法判断值是否满足配置的规则如果满足保存对应表的数据源映射最终返回满足条件的数据源映射 4.2遍历当前路由上下文的路由单元替换数据源映射中的真实数据源为影子数据源名称 关于本篇内容你有什么自己的想法或独到见解欢迎在评论区一起交流探讨下吧。