当前位置: 首页 > news >正文

【源码】Sharding-JDBC源码分析之SQL中影子库ShadowSQLRouter路由的原理

 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 SQLRouter<ShadowRule> {@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 Class<ShadowRule> 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 Collection<SimpleTableSegment> getAllTables() {return insertStatementContext.getAllTables();}@Overrideprotected ShadowOperationType getShadowOperationType() {return ShadowOperationType.INSERT;}/*** 获取插入语句中的注释段信息* @return*/@Overrideprotected Optional<Collection<String>> parseSQLComments() {Collection<String> result = new LinkedList<>();insertStatementContext.getSqlStatement().getCommentSegments().forEach(each -> result.add(each.getText()));return result.isEmpty() ? Optional.empty() : Optional.of(result);}@Overrideprotected Iterator<Optional<ShadowColumnCondition>> getShadowColumnConditionIterator(final String shadowColumn) {return new ShadowColumnConditionIterator(shadowColumn, parseColumnNames().iterator(), insertStatementContext.getInsertValueContexts());}/*** 解析插入语句中的参数名称* @return*/private Collection<String> parseColumnNames() {return insertStatementContext.getInsertColumnNames();}/*** 影子列条件迭代器*/private class ShadowColumnConditionIterator implements Iterator<Optional<ShadowColumnCondition>> {private int index;// 影子列private final String shadowColumn;// 插入语句中的列名迭代器private final Iterator<String> iterator;// 插入值上下文集合。每个插入值上下文中包含单条记录的参数信息private final List<InsertValueContext> insertValueContexts;ShadowColumnConditionIterator(final String shadowColumn, final Iterator<String> iterator, final List<InsertValueContext> insertValueContexts) {index = 0;this.shadowColumn = shadowColumn;this.iterator = iterator;this.insertValueContexts = insertValueContexts;}@Overridepublic boolean hasNext() {return iterator.hasNext();}/*** 遍历下一个参数,如果不是影子列,返回空* @return*/@Overridepublic Optional<ShadowColumnCondition> next() {String columnName = iterator.next();// 不是影子列,返回空if (!shadowColumn.equals(columnName)) {index++;return Optional.empty();}// 如果是影子列Optional<Collection<Comparable<?>>> columnValues = getColumnValues(insertValueContexts, index);index++;return columnValues.map(each -> new ShadowColumnCondition(getSingleTableName(), columnName, each));}/*** 获取列的值* @param insertValueContexts* @param columnIndex 当前投影列在列名集合中的下标* @return*/private Optional<Collection<Comparable<?>>> getColumnValues(final List<InsertValueContext> insertValueContexts, final int columnIndex) {Collection<Comparable<?>> 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 Map<String, 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 Map<String, String> findShadowDataSourceMappings(final ShadowRule shadowRule) {// 从SQL语句中的所有表,查找配置影子规则的表Collection<String> relatedShadowTables = getRelatedShadowTables(getAllTables(), shadowRule);if (relatedShadowTables.isEmpty() && isMatchDefaultShadowAlgorithm(shadowRule)) {return shadowRule.getAllShadowDataSourceMappings();}ShadowOperationType shadowOperationType = getShadowOperationType();// 判断 sql 注释中是否有shadow的提示。如 影子算法为 SIMPLE_HINT,且sql语句中添加了 /* SHARDINGSPHERE_HINT: SHADOW=true */Map<String, String> result = findBySQLComments(relatedShadowTables, shadowRule, shadowOperationType);if (!result.isEmpty()) {return result;}// 查找表中的是否存在满足条件的影子列,如果存在,则返回对应表的数据源映射return findByShadowColumn(relatedShadowTables, shadowRule, shadowOperationType);}/*** 结合配置的规则,查找配置了影子规则的表* @param simpleTableSegments* @param shadowRule* @return*/private Collection<String> getRelatedShadowTables(final Collection<SimpleTableSegment> simpleTableSegments, final ShadowRule shadowRule) {Collection<String> 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) {Optional<Collection<String>> sqlComments = parseSQLComments();// 如果没有设置注释段,返回falseif (!sqlComments.isPresent()) {return false;}// 获取默认影子算法Optional<ShadowAlgorithm> 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((HintShadowAlgorithm<Comparable<?>>) shadowAlgorithm, shadowDetermineCondition.initSQLComments(sqlComments.get()), shadowRule);}}return false;}/*** 查找SQL的注释部分,获取数据源信息。key为生产数据源、value为影子数据源* @param relatedShadowTables sql语句中配置了影子规则的表* @param shadowRule 影子规则对象* @param shadowOperationType 当前sql操作的类型* @return*/private Map<String, String> findBySQLComments(final Collection<String> relatedShadowTables, final ShadowRule shadowRule, final ShadowOperationType shadowOperationType) {Map<String, String> result = new LinkedHashMap<>();// 遍历影子表for (String each : relatedShadowTables) {// 判断sql的注释中是否包含了shadow信息,如 /* SHARDINGSPHERE_HINT: SHADOW=true */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 Collection<HintShadowAlgorithm<Comparable<?>>> shadowAlgorithms, final ShadowDetermineCondition shadowCondition, final ShadowRule shadowRule) {// 遍历hint影子算法for (HintShadowAlgorithm<Comparable<?>> each : shadowAlgorithms) {// 判断是否满足hint影子算法中的影子规则// 默认的hint影子算法为SimpleHintShadowAlgorithm,配置的type为SIMPLE_HINT// 该算法解析传入的shadowCondition中的sqlComment是否存在 /* SHARDINGSPHERE_HINT: SHADOW=true */ 的信息// 即判断对应的sql语句是否有 /* SHARDINGSPHERE_HINT: SHADOW=true */ 相关注解信息if (HintShadowAlgorithmDeterminer.isShadow(each, shadowCondition, shadowRule)) {return true;}}return false;}/*** 通过影子列查找数据源映射,key为生产数据源、value为影子数据源* @param relatedShadowTables 相关影子表* @param shadowRule 影子规则* @param shadowOperationType 影子操作类型* @return*/private Map<String, String> findByShadowColumn(final Collection<String> relatedShadowTables, final ShadowRule shadowRule, final ShadowOperationType shadowOperationType) {Map<String, String> result = new LinkedHashMap<>();// 遍历for (String each : relatedShadowTables) {// 获取相关影子表的影子列名称Collection<String> 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 Collection<String> 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 列的影子算法Collection<ColumnShadowAlgorithm<Comparable<?>>> columnShadowAlgorithms = shadowRule.getRelatedColumnShadowAlgorithms(shadowOperationType, shadowTable, shadowColumn);if (columnShadowAlgorithms.isEmpty()) {return false;}// 获取影子列的条件迭代器Iterator<Optional<ShadowColumnCondition>> iterator = getShadowColumnConditionIterator(shadowColumn);ShadowDetermineCondition shadowDetermineCondition;// 遍历迭代器while (iterator.hasNext()) {// 获取SQL中下一个列的条件信息Optional<ShadowColumnCondition> next = iterator.next();// 如果找到影子列if (next.isPresent()) {for (ColumnShadowAlgorithm<Comparable<?>> each : columnShadowAlgorithms) {// 创建一个影子确定条件对象shadowDetermineCondition = new ShadowDetermineCondition(shadowTable, shadowOperationType);// 通过决策器进行影子判断if (ColumnShadowAlgorithmDeterminer.isShadow(each, shadowDetermineCondition.initShadowColumnCondition(next.get()))) {return true;}}}}return false;}/*** 获取sql语句中所有的表* @return*/protected abstract Collection<SimpleTableSegment> getAllTables();/*** 获取影子库映射类型,即当前sql语句的类型。如INSERT、UPDATE等* @return*/protected abstract ShadowOperationType getShadowOperationType();/*** 解析SQL语句的注释信息* @return*/protected abstract Optional<Collection<String>> parseSQLComments();/*** 获取投影列的条件迭代器* @param shadowColumn* @return*/protected abstract Iterator<Optional<ShadowColumnCondition>> 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

小结

以上为本篇分析的全部内容,以下做一个小结:

1)ShardingSphere 影子库是一个在数据库层面解决全链路在线压测问题的有效工具,影子库用于接收测试数据,以防止测试数据污染生产数据库;

2)影子库规则对应的路由器对象为ShadowSQLRouter,在ShardingSpherePreparedStatement执行SQL语句进行SQL路由创建RouteContext路由上下文时最后执行的路由器;

3)影子库路由器中,通过ShadowRouteEngineFactory的newInstance()创建影子库路由引擎;

不同的SQL操作语句,创建不同的路由引擎,如插入语句,创建ShadowInsertStatementRoutingEngine;

4)ShadowInsertStatementRoutingEngine插入语句的路由引擎,继承于抽象类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)遍历当前路由上下文的路由单元,替换数据源映射中的真实数据源为影子数据源名称;

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

相关文章:

【源码】Sharding-JDBC源码分析之SQL中影子库ShadowSQLRouter路由的原理

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、【…...

OCR实践-Table-Transformer

前言 书接上文 OCR实践—PaddleOCR Table-Transformer 与 PubTables-1M table-transformer&#xff0c;来自微软&#xff0c;基于Detr&#xff0c;在PubTables1M 数据集上进行训练&#xff0c;模型是在提出数据集同时的工作&#xff0c; paper PubTables-1M: Towards comp…...

代码随想录五刷day6

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、力扣144. 二叉树的前序遍历(递归)二、力扣144. 二叉树的前序遍历(迭代)三、力扣145. 二叉树的后序遍历(递归)四、力扣145. 二叉树的后序遍历(迭代)五、力扣…...

【自信息、信息熵、联合熵、条件熵、互信息】

文章目录 一、自信息 I(X)二、信息熵&#xff1a;衡量系统的混乱程度信息熵 H(X)联合熵 H(X,Y) 三、条件熵H(Y|X) 联合熵H(X,Y) - 信息熵H(X)四、互信息 I(X,Y)五、总结References 一、自信息 I(X) 自信息(Self-information) 是由香农提出的&#xff0c;用来衡量单一事件发生…...

我的秋招总结

我的秋招总结 个人背景 双非本&#xff0c;985硕&#xff0c;科班 准备情况 以求职为目的学习Java的时间大概一年。 八股&#xff0c;一开始主要是看B站黑马的八股文课程&#xff0c;背JavaGuide和小林coding还有面试鸭。 算法&#xff0c;250&#xff0c;刷了3遍左右 项目&…...

page_ref_freeze浅析

最近在研究struct page的引用计数refcount&#xff0c;看到有个page_ref_freeze()特性很有意思。用这篇博客记录一下。 本文分析基于linux4.19.195 static inline int page_ref_freeze(struct page *page, int count) {int ret likely(atomic_cmpxchg(&page->_refcoun…...

Python毕业设计选题:基于python的酒店推荐系统_django+hadoop

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 用户管理 酒店客房管理 客房类型管理 客房预定管理 用户…...

选择 SquashFS 作为启动分区的文件系统格式:详细教程

SquashFS 是一种高压缩率的只读文件系统,广泛用于嵌入式系统、Linux 发行版以及其他需要节省存储空间的场景。它特别适合用于启动分区、只读根文件系统(rootfs)等应用,因为它通过压缩技术极大地节省了存储空间。在本博客中,我们将详细介绍如何在 RK3568 等嵌入式设备上使用…...

Unity 读Excel,读取xlsx文件解决方案

Unity读取表格数据 效果&#xff1a; 思路&#xff1a; Unity可以解析Json&#xff0c;但是读取Excel需要插件的帮助&#xff0c;那就把这个功能分离开&#xff0c;读表插件就只管读表转Json&#xff0c;Unity就只管Json解析&#xff0c;中间需要一个存储空间&#xff0c;使用…...

【C语言】指针数组、数组指针、函数指针、指针函数、函数指针数组、回调函数

【C语言】函数指针与指针函数 文章目录 [TOC](文章目录) 前言一、指针数组二、数组指针三、函数指针四、指针函数五、函数指针数组六、回调函数七、参考资料总结 前言 使用工具&#xff1a; 1.DEVC 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、…...

EasyExcel 模板+公式填充

使用 CellWriteHandler 的实现类来实现公式写入 Data NoArgsConstructor public class CustomCellWriteHandler implements CellWriteHandler {private int maxRowNum 2000;// 动态传入列表数量public CustomCellWriteHandler(int maxRowNum) {this.maxRowNum maxRowNum;}Ov…...

vue最新源码探索分析

我在github上fork了最新版本vue3.5版本的源码并做了大幅删除&#xff0c;保留最核心的代码&#xff0c;有兴趣的可以看看&#xff0c;欢迎大家提出PR 仓库地址 https://github.com/greatanimalion/core 本项目vue版本3.5.13 为了方便查看与分析&#xff0c;减少心智负担 已…...

产品初探Devops!以及AI如何赋能Devops?

DevOps源自Development&#xff08;开发&#xff09;和Operations&#xff08;运维&#xff09;的组合&#xff0c;是一种新的软件工程理念&#xff0c;旨在打破传统软件工程方法中“开发->测试->运维”的割裂模式&#xff0c;强调端到端高效一致的交付流程&#xff0c;实…...

深入解析MVCC中Undo Log版本底层存储读取逻辑

一、引言 多版本并发控制&#xff08;MVCC&#xff0c;Multi-Version Concurrency Control&#xff09;是一种广泛应用于关系数据库管理系统中的并发控制技术。它通过保存数据的历史版本&#xff0c;使得在事务并发执行时&#xff0c;每个事务都能看到数据的一致性视图。在MVC…...

【生产问题记录-Mysql分区】

描述 月报是根据日报的数据统计出来的&#xff0c;但是今天早上发现月报没有数据&#xff0c;日报是有数据的&#xff0c;那么为什么会导致这个结果呢&#xff1f; 问题解决 设计 因为日报table_day每天的数据量都在60w&#xff0c;所以我们采用了分区的形式&#xff0c;进…...

大型语言模型(LLMs)演化树 Large Language Models

大型语言模型&#xff08;LLMs&#xff09;演化树 Large Language Models flyfish 下面的图来自论文地址 Transformer 模型&#xff08;如 BERT 和 GPT-3&#xff09;已经给自然语言处理&#xff08;NLP&#xff09;领域带来了革命性的变化。这得益于它们具备并行化能力&…...

【LeetCode: 3159. 查询数组中元素的出现位置 + 统计下标】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…...

git自动压缩提交的脚本

可以将当前未提交的代码自动执行 git addgit commitgit squash Git 命令安装指南 1. 创建脚本目录 如果目录不存在&#xff0c;创建它&#xff1a; mkdir -p ~/.local/bin2. 创建脚本文件 vim ~/.local/bin/git-squash将完整的脚本代码复制到此文件中。 3. 设置脚本权限…...

uniapp中Nvue白屏问题 ReferenceError: require is not defined

uniapp控制台输出如下 exception function:createInstanceContext, exception:white screen cause create instanceContext failed,check js stack ->Uncaught ReferenceError: require is not defined 或者 exception function:createInstanceContext, exception:white s…...

Centos8安装图形化界面

由于Centos8已经停止维护&#xff0c;所以在使用的时候会遇到yum元数据找不到的情况 1、更新yum数据源 进入目录&#xff1a; cd /etc/yum.repos.d/ 修改文件&#xff1a; sed -i s/mirrorlist/#mirrorlist/g /etc/yum.repos.d/CentOS-* sed -i s|#baseurlhttp://mirror.cent…...

2023年厦门市第30届小学生C++信息学竞赛复赛上机操作题(三、2023C. 太空旅行(travel))

#include <bits/stdc.h>using namespace std;struct Ship {int u; // 从地球到火星的时间int v; // 从火星到天王星的时间 };// 自定义比较函数 bool cmp(const Ship &a, const Ship &b) {return a.u max(a.v, b.u) b.v < b.u max(b.v, a.u) a.v; }int ma…...

Doris的SQL原理解析

今天来介绍下Doris的SQL原理解析&#xff0c;主要从语法、解析、分析、执行等几个方面来介绍&#xff0c;可以帮助大家对Doris底层有个清晰的理解~ 一、Doris简介 Apache Doris是一个基于MPP架构的高性能、实时的分析型数据库&#xff0c;能够较好的满足报表分析、即席查询、…...

【RAG实战】语言模型基础

语言模型赋予了计算机理解和生成人类语言的能力。它结合了统计学原理和深度神经网络技术&#xff0c;通过对大量的样本数据进行复杂的概率分布分析来学习语言结构的内在模式和相关性。具体地&#xff0c;语言模型可根据上下文中已出现的词序列&#xff0c;使用概率推断来预测接…...

探索 .idea 文件夹:Java Maven 工程的隐形守护者

一、.idea文件夹深度解析&#xff1a;IntelliJ IDEA项目配置的核心 在Java Maven工程的开发环境中&#xff0c;.idea文件夹扮演着举足轻重的角色。这是IntelliJ IDEA项目特有的一个配置文件夹&#xff0c;它包含了项目所需的各种配置信息&#xff0c;以确保项目能够在不同的开…...

JAVA代理模式和适配器模式

文章目录 Java 代理模式和适配器模式代理模式&#xff08;Proxy Pattern&#xff09;适配器模式&#xff08;Adapter Pattern&#xff09;代理模式和适配器模式的区别 代理模式的使用举例静态代理实现:用代理模式记录方法调用日志动态代理实现:使用 Java 动态代理记录方法调用日…...

Python大数据可视化:基于python大数据的电脑硬件推荐系统_flask+Hadoop+spider

开发语言&#xff1a;Python框架&#xff1a;flaskPython版本&#xff1a;python3.7.7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 管理员登录 管理员功能界面 价格区间界面 用户信息界面 品牌管理 笔记本管理 电脑主机…...

【YOLOv3】源码(train.py)

概述 主要模块分析 参数解析与初始化 功能&#xff1a;解析命令行参数&#xff0c;设置训练配置项目经理制定详细的施工计划和资源分配日志记录与监控 功能&#xff1a;初始化日志记录器&#xff0c;配置监控系统项目经理使用监控和记录工具&#xff0c;实时跟踪施工进度和质量…...

一维、线性卡尔曼滤波的例程(MATLAB)

这段 MATLAB 代码实现了一维线性卡尔曼滤波器的基本功能&#xff0c;用于估计在存在噪声的情况下目标状态的真实值 文章目录 一维线性卡尔曼滤波代码运行代码介绍1. **初始化部分**2. **数据生成**3. **卡尔曼滤波器实现**4. **结果可视化**5. **统计输出** 源代码 总结 一维线…...

【Rust自学】6.2. Option枚举

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 6.2.1. 什么是Option枚举 它定义于标准库中&#xff0c;在Prelude&#xff08;预导入模块&#xff09;中&#xff0c;负责描述这样的场景…...

unity学习1:第1个template的项目platformer 学习

目录 0 教训&#xff0c;不要学生思路&#xff1a;路径依赖 1 从unity的编辑器里直接下载一个template 2 第一个下马威&#xff1a;下载到本地的这个模板项目第一次运行就报错, 其次关了重进就好了 2.1 报错 2.2 解决 2.3 解决 3 第2个拦路虎&#xff1a; 项目的声音大小…...

初识 Conda:一站式包管理和环境管理工具

文章目录 1. 什么是 Conda&#xff1f;2. 为什么选择 Conda&#xff1f;3. Conda 的安装3.1 安装步骤&#xff08;以 Miniconda 为例&#xff09; 4. Conda 的核心功能4.1 包管理4.2 环境管理4.3 Conda Forge4.4 设置国内镜像 5. 常见使用场景5.1 数据科学项目5.2 离线安装5.3 …...

vue.js 组件化开发 根组件

Vue.js是一个用于构建用户界面的渐进式JavaScript框架。组件化开发是Vue.js的核心理念之一&#xff0c;它允许开发者将部分代码封装为可重用的组件&#xff0c;从而提高代码的复用性和可维护性。而根组件是Vue.js应用的最顶层组件&#xff0c;它包含了其他所有的组件。 下面详…...

ASP.NET WebForms:实现全局异常捕获与处理的最佳实践

在ASP.NET WebForms中&#xff0c;你可以通过以下方法来统一捕获后台异常&#xff1a; 1. 在Global.asax中使用Application_Error Global.asax文件允许你处理应用程序级别的异常。你可以在Application_Error事件中捕获所有未处理的异常&#xff0c;并根据需要记录或处理它们。…...

vue3配置测试环境、开发环境、生产环境

第一步&#xff1a;在src同级新建 .env.production 、.env.test 、.env.development文件 第二步&#xff1a;在文件中配置开发环境、生产环境、测试环境 // 开发环境 .env.developmentNODE_ENV developmentVUE_APP_MODE development outputDir dist_dev // 打出包的名称VUE_…...

农历节日倒计时:基于Python的公历与农历日期转换及节日查询小程序(升级版)

农历节日倒计时&#xff1a;基于Python的公历与农历日期转换及节日查询小程序升级版 调整的功能 上一个小程序只是能计算当年的农历节日的间隔时间&#xff0c;那么这次修改一下&#xff0c;任意年份的农历节日都可以&#xff0c;并且能输出农历节日对应的阳历日期&#xff0…...

linux Python环境部署

登录Python官网去下载对应的版本&#xff1a;Python下载地址 在data目录下创建python文件夹 mkdir python上传下载的安装包 Python-3.8.18.tgz解压 tar -xf Python-3.8.18.tgz进入解压后的目录 cd Python-3.8.18/编译安装 ./configure --prefix/data/python38 make &&…...

Python基础语法知识——数据类型的查询、数据类型转化

今天第一次学习python&#xff0c;之前学习过C&#xff0c;感觉学习起来还可以&#xff0c;就是刚用的时候有点手残&#xff0c;想的是python代码&#xff0c;结果写出来就是C,本人决定每天抽出时间写点。同时继续更新NX二次开发专栏学习&#xff0c;话不多说&#xff0c;晚上的…...

命令行之巅:Linux Shell编程的至高艺术(中)

文章一览 前言一、输入/输出及重定向命令1.1 输入/输出命令1.1.1 read命令1.1.2 echo命令 1.2 输入/输出重定向1.3 重定向深入讲解1.4 Here Document1.4.1 /dev/null 文件 二、shell特殊字符和命令语法2.1 引号2.1.1 双引号2.1.2 单引号2.1.3 倒引号 2.2 注释、管道线和后台命令…...

利用Gurobi追溯模型不可行原因的四种方案及详细案例

文章目录 1. 引言2. 追溯不可行集的四种方法2.1 通过约束增减进行判断2.2 通过computeIIS函数获得冲突集2.3 利用 feasRelaxS() 或 feasRelax() 函数辅助排查2.4 利用 IIS Force 属性1. 引言 模型不可行是一个让工程师头疼的问题,对于复杂模型而言,导致模型不可行的原因可能…...

「matplotlib」绘制图线和数据点的样式风格和颜色表大全

绘制图线和数据点的样式风格和颜色表大全 显示图例 legend() 属性 linestyle 属性 marker 属性color 1、legend() 显示坐标轴中图线的对应标注的显示位置plt.legend(loc) locloc codebest &#xff08;default&#xff09;0upper right1upper left2lower left3lower rig…...

【基础还得练】 KKT 条件

优秀教程-真正理解拉格朗日乘子法和 KKT 条件&#xff1a; link优秀教程-最优化(6)&#xff1a;一般约束优化问题的最优性理论&#xff1a; link KKT条件&#xff08;Karush-Kuhn-Tucker条件&#xff09;是非线性规划中的一组必要条件&#xff0c;在某些情况下也是最优解的充分…...

Node.JS 版本管理工具 Fnm 安装及配置(Windows)

安装流程可参考&#xff1a;fnm 安装及配置(Windows)_fnm安装-CSDN博客 然后就是在git bash如何生效 在 Git Bash 中使用 fnm 需要确保你正确设置了环境变量。你可以按照以下步骤进行配置&#xff1a; 1. **打开 Git Bash**&#xff1a; 启动 Git Bash。 2. **编辑 .bas…...

STM32-笔记11-手写带操作系统的延时函数

1、为什么带操作系统的延时函数&#xff0c;和笔记10上的延时函数不能使用同一种&#xff1f; 因为笔记10的延时函数在每次调用的时候&#xff0c;会一直开关定时器&#xff0c;而在FreeRTOS操作系统中&#xff0c;SysTick定时器当作时基使用。 时基是一个时间显示的基本单位。…...

CCNP_SEC_ASA 第六天作业

实验需求&#xff1a; 为保障内部用户能够访问Internet&#xff0c;请把10.1.1.0/24网络动态转换到外部地址池202.100.1.100-202.100.1.200&#xff0c;如果地址池耗尽后&#xff0c;PAT到Outside接口 提示&#xff1a;需要看到如下输出信息 Inside#telnet 202.100.1.1 Trying …...

Python小括号( )、中括号[ ]和大括号{}代表什么

python语言最常见的括号有三种&#xff0c;分别是&#xff1a;小括号( )、中括号[ ]和大括号也叫做花括号{ }&#xff0c;分别用来代表不同的python基本内置数据类型。 小括号&#xff08;&#xff09;&#xff1a;struct结构体&#xff0c;但不能改值 python中的小括号( )&am…...

electron node-api addon开发

解决方案入口 拷贝日志以及json等第三方源码 增加包含目录 编写接口 默认模板已经有一个回调函数了 照葫芦画瓢就行 其中几个重要的点要注意 1.参数传入 比如如下的例子&#xff1a; 头文件定义&#xff1a; public:下增加 Napi::Value StartAnswer (const Napi::Callb…...

vue之axios基本使用

文章目录 1. axios 网络请求库2. axiosvue 1. axios 网络请求库 <body> <input type"button" value"get请求" class"get"> <input type"button" value"post请求" class"post"> <!-- 官网提供…...

uniapp中wx.getFuzzyLocation报错如何解决

一、用wx.getLocation接口审核不通过 用uniapp开发小程序时难免需要获取当前地理位置。 代码如下&#xff1a; uni.getLocation({type: wgs84,success: function (res) {console.log(当前位置的经度&#xff1a; res.longitude);console.log(当前位置的纬度&#xff1a; r…...

Peter Lax线性代数教材:Linear Algebra and Its Applications 2nd Ed

这本线性代数教材&#xff0c;印象很深刻&#xff0c;记得是高中时期自学线性代数的时候看过。这本书跟Gilbert Strang的教材MIT线性代数教材&#xff1a;Linear Algebra and Its Applications相比&#xff0c;内容没这么紧凑&#xff0c;而且表述也更加代数风格&#xff0c;很…...

vue3封装而成的APP ,在版本更新后,页面显示空白

一、问题展示 更新之后页面空白&#xff0c;打不开 &#xff0c;主要是由于缓存造成的 二、解决办法 1、随机数代码实现 使用随机数来动态的生成静态资源目录名可以避免浏览器缓存&#xff0c;但同时每次也会导致浏览器每次都下载最新的资源。如果静态资源过大&#xff0c;可…...