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

《ShardingSphere解读》13 路由引擎:如何理解分片路由核心类 ShardingRouter 的运作机制?

前面我们对 ShardingSphere 中的 SQL 解析引擎做了介绍我们明白 SQL 解析的作用就是根据输入的 SQL 语句生成一个 SQLStatement 对象。从今天开始我们将进入ShardingSphere 的路由Routing引擎部分的源码解析。从流程上讲路由引擎是整个分片引擎执行流程中的第二步即基于 SQL 解析引擎所生成的 SQLStatement通过解析执行过程中所携带的上下文信息来获取匹配数据库和表的分片策略并生成路由结果。分层路由引擎整体架构与介绍 SQL 解析引擎时一样我们通过翻阅 ShardingSphere 源码首先梳理了如下所示的包结构上述包图总结了与路由机制相关的各个核心类我们可以看到整体呈一种对称结构即根据是PreparedStatement还是普通 Statement分成两个分支流程。同时我们也可以把这张图中的类按照其所属的包结构分成两个层次位于底层的 sharding-core-route 和位于上层的 sharding-core-entry这也是 ShardingSphere 中所普遍采用的一种分包原则即根据类的所属层级来组织包结构。关于 ShardingSphere 的分包原则我们在 [《12 | 从应用到原理如何高效阅读 ShardingSphere 源码》]中也已经进行了介绍接下来我们具体分析这一原则在路由引擎中的应用。1.sharding-core-route 工程我们先来看图中的 ShardingRouter 类该类是整个路由流程的启动点。ShardingRouter 类直接依赖于解析引擎 SQLParseEngine 类完成 SQL 解析并获取 SQLStatement 对象然后供 PreparedStatementRoutingEngine 和 StatementRoutingEngine 进行使用。注意到这几个类都位于 sharding-core-route 工程中处于底层组件。2.sharding-core-entry 工程另一方面上图中的 PreparedQueryShardingEngine 和 SimpleQueryShardingEngine 则位于 sharding-core-entry 工程中。从包的命名上看entry 相当于是访问的入口所以我们可以判断这个工程中所提供的类属于面向应用层组件处于更加上层的位置。PreparedQueryShardingEngine 和 SimpleQueryShardingEngine 的使用者分别是 ShardingPreparedStatement 和 ShardingStatement。这两个类再往上就是 ShardingConnection 以及 ShardingDataSource 这些直接面向应用层的类了。路由核心类ShardingRouter通过以上分析我们对路由引擎的整体结构有了一个初步的认识。对于采用分层结构的执行流程而言有两种解析思路即自上而下或自下而上。今天我们的思路是从底层出发逐层往上分析流程的链路先来看路由引擎中最底层的对象 ShardingRouter变量定义如下private final ShardingRule shardingRule; private final ShardingSphereMetaData metaData; private final SQLParseEngine parseEngine;在 ShardingRouter 中我们首先看到了熟悉的 SQL 解析引擎 SQLParseEngine 以及它的使用方法public SQLStatement parse(final String logicSQL, final boolean useCache) { return parseEngine.parse(logicSQL, useCache); }上述代码非常简单即通过 SQLParseEngine 对传入的 SQL 进行解析返回一个 SQLStatement 对象。这里将 SQL 命名为 logicSQL以便区别在分片和读写分离情况下的真实 SQL。接下来我们来看一下 ShardingRule请注意这是一个基础类代表着分片的各种规则信息。ShardingRule 类位于 sharding-core-common 工程中主要保存着与分片相关的各种规则信息以及 ShardingKeyGenerator 等分布式主键的创建过程各个变量定义以及对应的注释如下所示//分片规则配置类封装各种配置项信息 private final ShardingRuleConfiguration ruleConfiguration; //DataSource 名称列表 private final ShardingDataSourceNames shardingDataSourceNames; //针对表的规则列表 private final Collection tableRules; //针对绑定表的规则列表 private final Collection bindingTableRules; //广播表名称列表 private final Collection broadcastTables; //默认的数据库分片策略 private final ShardingStrategy defaultDatabaseShardingStrategy; //默认的数据表分片策略 private final ShardingStrategy defaultTableShardingStrategy; //默认的分片键生成器 private final ShardingKeyGenerator defaultShardingKeyGenerator; //针对读写分离的规则列表 private final Collection masterSlaveRules; //加密规则 private final EncryptRule encryptRule;ShardingRule 的内容非常丰富但其定位更多是提供规则信息而不属于核心流程因此我们先不对其做详细展开。作为基础规则类ShardingRule 会贯穿整个分片流程在后续讲解过程中我们会穿插对它的介绍这里先对上述变量的名称和含义有简单认识即可。我们回到 ShardingRouter 类发现其核心方法只有一个即 route 方法。这个方法的逻辑比较复杂我们梳理它的执行步骤如下图所示ShardingRouter 是路由引擎的核心类在接下来的内容中我们将对上图中的 6 个步骤分别一 一 详细展开帮忙你理解一个路由引擎的设计思想和实现机制。1.分片合理性验证我们首先来看 ShardingRouter 的第一个步骤即验证分片信息的合理性验证方式如下所示//使用ShardingStatementValidator对Statement进行验证 Optional shardingStatementValidator ShardingStatementValidatorFactory.newInstance(sqlStatement); if (shardingStatementValidator.isPresent()) { shardingStatementValidator.get().validate(shardingRule, sqlStatement, parameters); }这段代码使用 ShardingStatementValidator 对输入的 SQLStatement 进行验证可以看到这里用到了典型的工厂模式工厂类 ShardingStatementValidatorFactory 如下所示public final class ShardingStatementValidatorFactory { public static Optional newInstance(final SQLStatement sqlStatement) { if (sqlStatement instanceof InsertStatement) { return Optional.of(new ShardingInsertStatementValidator()); } if (sqlStatement instanceof UpdateStatement) { return Optional.of(new ShardingUpdateStatementValidator()); } return Optional.absent(); } }注意到 ShardingStatementValidator 要验证的只有 InsertStatement 和 UpdateStatement 这两个 SQLStatement。那么如何进行验证呢我们来看一下 ShardingStatementValidator 的定义如下所示public interface ShardingStatementValidator { //验证分片操作是否支持 void validate(ShardingRule shardingRule, T sqlStatement, List parameters); }对于验证过程而言核心思想在于根据 SQLStatement 中的 Segment 与 ShardingRule 中的规则来判断它们之间是否有需要特殊处理的判断逻辑。我们以 ShardingInsertStatementValidator 为例来看验证过程它的 validate 方法如下所示public final class ShardingInsertStatementValidator implements ShardingStatementValidator { Override public void validate(final ShardingRule shardingRule, final InsertStatement sqlStatement, final List parameters) { Optional onDuplicateKeyColumnsSegment sqlStatement.findSQLSegment(OnDuplicateKeyColumnsSegment.class); //如果是ON DUPLICATE KEY UPDATE语句且如果当前操作的是分片Column时验证不通过 if (onDuplicateKeyColumnsSegment.isPresent() isUpdateShardingKey(shardingRule, onDuplicateKeyColumnsSegment.get(), sqlStatement.getTable().getTableName())) { throw new ShardingException(INSERT INTO .... ON DUPLICATE KEY UPDATE can not support update for sharding column.); } } … }可以看到这里的判断逻辑与“ON DUPLICATE KEY UPDATE”这一 Mysql 特有的语法相关该语法允许我们通过 Update 的方式插入有重复主键的数据行实际上这个语法也不是常规语法本身也不大应该被使用。ShardingInsertStatementValidator 先判断是否存在 OnDuplicateKeyColumn然后再判断这个 Column 是否是分片键如果同时满足这两个条件则直接抛出一个异常不允许在分片 Column 上执行“INSERT INTO …. ON DUPLICATE KEY UPDATE”语法。2.获取上下文接下来我们来看 ShardingRouter 类中 route 方法的第二段代码该段代码比较简单用于获取运行时的 SQLStatement 上下文如下所示//获取 SQLStatementContext SQLStatementContext sqlStatementContext SQLStatementContextFactory.newInstance(metaData.getRelationMetas(), logicSQL, parameters, sqlStatement);可以看到这里构建了上下文对象 SQLStatementContext同样用到了工厂模式工厂类 SQLStatementContextFactory 如下所示public final class SQLStatementContextFactory { public static SQLStatementContext newInstance(final RelationMetas relationMetas, final String sql, final List parameters, final SQLStatement sqlStatement) { if (sqlStatement instanceof SelectStatement) { return new SelectSQLStatementContext(relationMetas, sql, parameters, (SelectStatement) sqlStatement); } if (sqlStatement instanceof InsertStatement) { return new InsertSQLStatementContext(relationMetas, parameters, (InsertStatement) sqlStatement); } return new CommonSQLStatementContext(sqlStatement); } }请注意 SQLStatementContext 只有三种SelectSQLStatementContextInsertSQLStatementContextCommonSQLStatementContext它们都实现了 SQLStatementContext 接口顾名思义所谓的SQLStatementContext 就是一种上下文对象保存着与特定 SQLStatement 相关的上下文信息用于为后续处理提供数据存储和传递的手段。我们可以想象在 SQLStatementContext 中势必都持有 SQLStatement 对象以及与表结构信息相关的上下文 TablesContext。对于 SelectSQLStatement通常也需要保存与查询相关的分组上下文 GroupByContext、排序上下文 OrderByContext 和分页上下文 PaginationContext而对于InsertSQLStatementContext 而言InsertValueContext 则包含了所有与插入操作相关的值对象。3.自动生成主键接下来的第三段代码与数据库主键相关同样只有一句代码如下所示//如果是 InsertStatement 则自动生成主键 Optional generatedKey sqlStatement instanceof InsertStatement ? GeneratedKey.getGenerateKey(shardingRule, metaData.getTables(), parameters, (InsertStatement) sqlStatement) : Optional.absent();这段代码的逻辑比较明确即如果输入的 SQLStatement 是 InsertStatement则自动创建一个主键 GeneratedKey反之就不做处理。在数据分片的场景下创建一个分布式主键实际上并没有那么简单所以在这段代码背后有很多设计的思想和实现的技巧值得我们进行深入分析关于这个主题我们已经在 [《14 | 分布式主键ShardingSphere 中有哪些分布式主键实现方式》]中对分布式主键生成机制做了专题分享。4.创建分片条件我们来看 ShardingRouter 中 route 方法的第四个步骤这个步骤的作用是创建分片条件如下所示//创建分片条件 ShardingConditions shardingConditions getShardingConditions(parameters, sqlStatementContext, generatedKey.orNull(), metaData.getRelationMetas()); boolean needMergeShardingValues isNeedMergeShardingValues(sqlStatementContext); if (sqlStatementContext.getSqlStatement() instanceof DMLStatement needMergeShardingValues) { checkSubqueryShardingValues(sqlStatementContext, shardingConditions); mergeShardingConditions(shardingConditions); }在 ShardingSphere 中分片条件对象 ShardingCondition 定义如下所示包含了一组路由信息和节点信息其中路由信息包含表名和列名而节点信息包含数据源名和表名public class ShardingCondition { //路由信息 private final List routeValues new LinkedList(); //节点信息 private final Collection dataNodes new LinkedList(); }那么如何获取分片条件呢如下所示的 getShardingConditions 方法给出了具体的实现方式可以看到这里根据输入的 SQL 类型分别通过 InsertClauseShardingConditionEngine 和WhereClauseShardingConditionEngine 创建了 ShardingConditionsprivate ShardingConditions getShardingConditions(final List parameters, final SQLStatementContext sqlStatementContext, final GeneratedKey generatedKey, final RelationMetas relationMetas) { if (sqlStatementContext.getSqlStatement() instanceof DMLStatement) { //如果是 InsertSQLStatement 上下文 if (sqlStatementContext instanceof InsertSQLStatementContext) { InsertSQLStatementContext shardingInsertStatement (InsertSQLStatementContext) sqlStatementContext; //通过 InsertClauseShardingConditionEngine 创建分片条件 return new ShardingConditions(new InsertClauseShardingConditionEngine(shardingRule).createShardingConditions(shardingInsertStatement, generatedKey, parameters)); } //否则直接通过 WhereClauseShardingConditionEngine 创建分片条件 return new ShardingConditions(new WhereClauseShardingConditionEngine(shardingRule, relationMetas).createShardingConditions(sqlStatementContext.getSqlStatement(), parameters)); } return new ShardingConditions(Collections.emptyList()); }对于路由引擎而言分片条件的主要目的就是提取用于路由的目标数据库、表和列之间的关系InsertClauseShardingConditionEngine 和 WhereClauseShardingConditionEngine 中的处理逻辑都是为了构建包含这些关系信息的一组 ShardingCondition 对象。当获取这些 ShardingCondition 之后我们还看到有一个优化的步骤即调用mergeShardingConditions对可以合并的 ShardingCondition 进行合并。5.执行路由当我们获取了 SQLStatement 上下文并创建了分片条件接下来就是真正执行路由如下所示//获取 RoutingEngine 并执行路由 RoutingEngine routingEngine RoutingEngineFactory.newInstance(shardingRule, metaData, sqlStatementContext, shardingConditions); RoutingResult routingResult routingEngine.route();这两句代码是 ShardingRouter 类的核心我们获取了一个 RoutingEngine 实例然后基于该实例执行路由并返回一个 RoutingResult 对象。RoutingEngine 定义如下只有一个简单的 route 方法public interface RoutingEngine { //执行路由 RoutingResult route(); }在 ShardingSphere 中存在一批 RoutingEngine 的实现类RoutingEngineFactory 工厂类负责生成这些具体的 RoutingEngine生成逻辑如下所示public static RoutingEngine newInstance(final ShardingRule shardingRule, final ShardingSphereMetaData metaData, final SQLStatementContext sqlStatementContext, final ShardingConditions shardingConditions) { SQLStatement sqlStatement sqlStatementContext.getSqlStatement(); Collection tableNames sqlStatementContext.getTablesContext().getTableNames(); //全库路由 if (sqlStatement instanceof TCLStatement) { return new DatabaseBroadcastRoutingEngine(shardingRule); } //全库表路由 if (sqlStatement instanceof DDLStatement) { return new TableBroadcastRoutingEngine(shardingRule, metaData.getTables(), sqlStatementContext); } //阻断路由 if (sqlStatement instanceof DALStatement) { return getDALRoutingEngine(shardingRule, sqlStatement, tableNames); } //全实例路由 if (sqlStatement instanceof DCLStatement) { return getDCLRoutingEngine(shardingRule, sqlStatementContext, metaData); } //默认库路由 if (shardingRule.isAllInDefaultDataSource(tableNames)) { return new DefaultDatabaseRoutingEngine(shardingRule, tableNames); } //全库路由 if (shardingRule.isAllBroadcastTables(tableNames)) { return sqlStatement instanceof SelectStatement ? new UnicastRoutingEngine(shardingRule, tableNames) : new DatabaseBroadcastRoutingEngine(shardingRule); } //默认库路由 if (sqlStatementContext.getSqlStatement() instanceof DMLStatement tableNames.isEmpty() shardingRule.hasDefaultDataSourceName()) { return new DefaultDatabaseRoutingEngine(shardingRule, tableNames); } //单播路由 if (sqlStatementContext.getSqlStatement() instanceof DMLStatement shardingConditions.isAlwaysFalse() || tableNames.isEmpty() || !shardingRule.tableRuleExists(tableNames)) { return new UnicastRoutingEngine(shardingRule, tableNames); } //分片路由 return getShardingRoutingEngine(shardingRule, sqlStatementContext, shardingConditions, tableNames); }这些 RoutingEngine 的具体介绍我们放在下一篇《18 | 路由引擎如何实现数据访问的分片路由和广播路由》中进行详细介绍这里只需要了解 ShardingSphere 在包结构的设计上把具体的 RoutingEngine 分成了六大类即广播broadcast路由、混合complex路由、默认数据库defaultdb路由、无效ignore路由、标准standard路由以及单播unicast路由如下所示不同类型的 RoutingEngine 实现类RoutingEngine 的执行结果是 RoutingResult而 RoutingResult 中包含了一个 RoutingUnit集合RoutingUnit 中的变量定义如下所示可以看到有两个关于 DataSource 名称的变量以及一个 TableUnit 列表//真实数据源名 private final String dataSourceName; //逻辑数据源名 private final String masterSlaveLogicDataSourceName; //表单元列表 private final List tableUnits new LinkedList();而 TableUnit 保存着逻辑表名和实际表名如下所示public final class TableUnit { //逻辑表名 private final String logicTableName; //真实表名 private final String actualTableName; }所以 RoutingResult 中保存的实际上就是一组关于数据库与数据表的对应关系其中库与表都存在逻辑值和真实值。6.构建路由结果当通过一系列的路由引擎处理之后我们获得了 RoutingResult 对象但并不是直接将其进行返回而是会构建一个 SQLRouteResult 对象。这就是 ShardingRouter 的 route 方法最后一个步骤如下所示//构建 SQLRouteResult SQLRouteResult result new SQLRouteResult(sqlStatementContext, shardingConditions, generatedKey.orNull()); result.setRoutingResult(routingResult); //如果是Insert语句则设置自动生成的分片键 if (sqlStatementContext instanceof InsertSQLStatementContext) { setGeneratedValues(result); } return result;我们来到 SQLRouteResult 的定义看看它与 RouteResult 之间有什么不同SQLRouteResult中 的变量如下所示//SQLStatement 上下文 private final SQLStatementContext sqlStatementContext; //分片条件 private final ShardingConditions shardingConditions; //自动生成的分片键 private final GeneratedKey generatedKey; //一组路由单元 private final Collection routeUnits new LinkedHashSet(); //由 RoutingEngine 生成的 RoutingResult private RoutingResult routingResult;可以看到 SQLRouteResult 中包含了 RoutingResult。我们可以认为 SQLRouteResult 是整个 SQL 路由返回的路由结果在后续的流程中还会被 PreparedStatementRoutingEngine 等上层对象所使用而 RoutingResult 只是 RoutingEngine 返回的路由结果它的使用者就是位于底层的 ShardingRouter。同时我们注意到这里有一个新的 Unit 对象 RouteUnit包含了数据源名称以及 SQL 单元对象 SQLUnit如下所示public final class RouteUnit { //数据源名 private final String dataSourceName; //SQL 单元 private final SQLUnit sqlUnit; }这里的 SQLUnit 中就是最终的一条 SQL 语句以及相应参数的组合。因为路由结果对象 SQLRouteResult 会继续传递到分片引擎的后续流程且内部结构比较复杂所以这里通过如下所示的类图对其包含的各种变量进行总结方便你进行理解。至此我们把 ShardingRouter 类的核心流程做了介绍。在 ShardingSphere 的路由引擎中ShardingRouter 可以说是一个承上启下的核心类向下我们可以挖掘各种 RoutingEngine 的具体实现向上我们可以延展到读写分离等面向应用的具体场景。下图展示了 ShardingRouter 的这种定位关系。关于各种 RoutingEngine 的介绍是我们下一篇的内容今天我们先将基于 ShardingRouter 讨论它的上层结构从而引出了 ShardingEngine。从底层 ShardingRouter 到上层 ShardingEngine我们的思路仍然是从下往上先来看上图中的 StatementRoutingEngine其实现如下所示public final class StatementRoutingEngine { private final ShardingRouter shardingRouter; private final ShardingMasterSlaveRouter masterSlaveRouter; public StatementRoutingEngine(final ShardingRule shardingRule, final ShardingSphereMetaData metaData, final SQLParseEngine sqlParseEngine) { shardingRouter new ShardingRouter(shardingRule, metaData, sqlParseEngine); masterSlaveRouter new ShardingMasterSlaveRouter(shardingRule.getMasterSlaveRules()); } public SQLRouteResult route(final String logicSQL) { SQLStatement sqlStatement shardingRouter.parse(logicSQL, false); return masterSlaveRouter.route(shardingRouter.route(logicSQL, Collections.emptyList(), sqlStatement)); } }可以看到在 StatementRoutingEngine 的 route 方法中通过 ShardingMasterSlaveRouter 对通过 ShardingRouter 所生成的 SQLRouteResult 进行了再一次路由也就是说在分片路由的基础上添加了主从路由关于读写分离和主从路由我们会在之后的《26 | 读写分离普通主从架构和分片主从架构分别是如何实现的》进行讨论。现在我们来到 sharding-core-entry 工程看看更上层的处理流程。整个 sharding-core-entry 工程只有三个类即作为基类的 BaseShardingEngine 以及两个子类 PreparedQueryShardingEngine 和 SimpleQueryShardingEngine。我们先来看 BaseShardingEngine 类它本质上是一个模板类BaseShardingEngine 的 shard 方法如下所示public SQLRouteResult shard(final String sql, final List parameters) { //调用模板方法准备参数 List clonedParameters cloneParameters(parameters); //执行路由 SQLRouteResult result executeRoute(sql, clonedParameters); //执行 SQL 转换Convert和改写Rewrite result.getRouteUnits().addAll(HintManager.isDatabaseShardingOnly() ? convert(sql, clonedParameters, result) : rewriteAndConvert(sql, clonedParameters, result)); //省略日志记录 return result; }在这里我们看到了 SQL 转换Convert和改写Rewrite的入口这是路由引擎之外的执行流程我们今天不做展开。上述代码与路由相关最核心的就是 executeRoute 方法如下所示private SQLRouteResult executeRoute(final String sql, final List clonedParameters) { routingHook.start(sql); try { //调用模板方法执行路由并获取结果 SQLRouteResult result route(sql, clonedParameters); routingHook.finishSuccess(result, metaData.getTables()); return result; } catch (final Exception ex) { routingHook.finishFailure(ex); throw ex; } }这个方法的处理方式与 SQLParseEngine 的 parse 方法有着类似的代码结构同样用到了 Hook 机制。从设计模式上讲BaseShardingEngine 采用了非常典型的模板方法。当我们需要完成一个过程或一系列步骤时这些过程或步骤在某一细节层次保持一致但个别步骤在更详细的层次上的实现可能不同时可以考虑用模板方法模式来处理。实现模板方法的过程也非常简单其实就是利用了类的继承机制。作为一个模板类我们注意到 BaseShardingEngine 提供了两个模板方法供子类进行实现分别是//拷贝参数 protected abstract List cloneParameters(List parameters); //执行路由 protected abstract SQLRouteResult route(String sql, List parameters);显然对于 SimpleQueryShardingEngine 而言不需要参数所以 cloneParameters 直接返回空列表。而 route 方法则直接使用前面介绍的 StatementRoutingEngine 进行路由。SimpleQueryShardingEngine 类的完整实现如下所示public final class SimpleQueryShardingEngine extends BaseShardingEngine { private final StatementRoutingEngine routingEngine; public SimpleQueryShardingEngine(final ShardingRule shardingRule, final ShardingProperties shardingProperties, final ShardingSphereMetaData metaData, final SQLParseEngine sqlParseEngine) { super(shardingRule, shardingProperties, metaData); routingEngine new StatementRoutingEngine(shardingRule, metaData, sqlParseEngine); } Override protected List cloneParameters(final List parameters) { return Collections.emptyList(); } Override protected SQLRouteResult route(final String sql, final List parameters) { return routingEngine.route(sql); } }至此关于 ShardingSphere 路由引擎部分的内容基本都介绍完毕。对于上层结构而言我们以 SimpleQueryShardingEngine 为例进行了展开对于 PreparedQueryShardingEngine 的处理方式也是类似。作为总结我们通过如下所示的时序图来梳理这些路由的主流程。从源码解析到日常开发分包设计原则可以用来设计和规划开源框架的代码结构。在今天的内容中我们看到了 ShardingSphere 中非常典型的一种分层和分包实现策略。通过 sharding-core-route 和 sharding-core-entry 这两个工程我们把路由引擎中位于底层的核心类 ShardingRouter 和位于上层的 PreparedQueryShardingEngine 及 SimpleQueryShardingEngine 类进行了合理的分层管理。ShardingSphere 对于分层和分包策略的应用有很多具体的表现形式后续有更多的应用场景。小结与预告作为 ShardingSphere 分片引擎的第二个核心组件路由引擎的目的在于生成 SQLRouteResult目标对象。而整个路由引擎中最核心的就是 ShardingRouter 类。今天我们对 ShardingRouter 的整体执行流程进行了详细的讨论同时也引出了路由引擎中的底层对象 RoutingEngine。这里给你留一道思考题ShardingSphere 中一个完整的路由执行过程需要经历哪些步骤欢迎你在留言区与大家讨论我将一一点评解答。本篇中我们也提到了 ShardingSphere 中存在多种 RoutingEngine。在下一篇的内容中我们将关注于这些 RoutingEngine 的具体实现过程。

相关文章:

《ShardingSphere解读》13 路由引擎:如何理解分片路由核心类 ShardingRouter 的运作机制?

前面我们对 ShardingSphere 中的 SQL 解析引擎做了介绍,我们明白 SQL 解析的作用就是根据输入的 SQL 语句生成一个 SQLStatement 对象。 从今天开始,我们将进入 ShardingSphere 的路由(Routing)引擎部分的源码解析。从流程上讲&am…...

Z-Image-GGUF部署教程:SSH端口转发+本地浏览器访问远程服务器完整流程

Z-Image-GGUF部署教程:SSH端口转发本地浏览器访问远程服务器完整流程 📝 最后更新:2026年2月26日 🎨 基于阿里通义实验室 Z-Image 模型 🔧 GGUF 量化版本,低显存友好 1. 快速开始:30秒上手文生图…...

vs code , 配置 claude code 插件, 默认选项 : --dangerously-skip-permission

文章目录 一、核心配置步骤(含风险提醒) 方式1:通过VS Code图形界面配置(新手友好) 方式2:直接编辑settings.json(精准配置) 验证配置是否生效 二、关键注意事项 总结 一、核心配置步骤(含风险提醒) 首先需要明确:--dangerously-skip-permission 是 Claude Code 插…...

DVWA 靶场实战:从零到一的 Web 安全攻防演练

1. DVWA靶场入门:Web安全攻防演练环境搭建 第一次接触DVWA时,我花了两小时才把环境跑起来。这个用PHP/MySQL编写的漏洞演练平台,简直是安全初学者的宝藏。下面分享我的踩坑经验,帮你10分钟搞定环境搭建。 核心组件准备&#xff1a…...

网络工程师实战:用iperf3做企业级网络质量检测(TCP/UDP全参数解析)

网络工程师实战:用iperf3做企业级网络质量检测(TCP/UDP全参数解析) 当企业网络出现视频会议卡顿、文件传输缓慢或云服务延迟时,传统的ping和traceroute往往只能给出"网络有问题"的模糊结论。作为网络工程师,…...

百川2-13B-4bits镜像免配置价值:省去Linux系统级CUDA驱动校验、Python虚拟环境创建等前置步骤

百川2-13B-4bits镜像免配置价值:省去Linux系统级CUDA驱动校验、Python虚拟环境创建等前置步骤 1. 引言:当大模型部署不再“劝退” 如果你曾经尝试在本地部署一个百亿参数级别的大语言模型,大概率会经历这样的“劝退”流程: 检查…...

算法对决:排序遍历 vs 分治法——谁才是众数查找的性能王者?

算法对决:排序遍历 vs 分治法——谁才是众数查找的性能王者? 在数据处理领域,众数查找是一个经典问题。面对百万级甚至更大规模的数据集时,算法选择直接影响系统性能。本文将深入剖析两种主流方法——排序遍历法和分治法&#xff…...

保姆级教程:使用SuperMap iClient for OpenLayers加载超图服务的完整流程(含坐标系转换技巧)

SuperMap iClient与OpenLayers融合开发实战:从坐标系原理到地图服务加载 当你第一次尝试将SuperMap的地图服务集成到OpenLayers项目中时,可能会被各种专业术语和坐标系问题搞得晕头转向。作为GIS开发领域的黄金组合,SuperMap iClient for Ope…...

MinIO纠删码EC策略怎么选?从数据安全与成本角度深度解析EC:2与EC:3

MinIO纠删码策略选型指南:EC:2与EC:3的深度权衡 在分布式存储系统的设计与运维中,数据安全与存储成本的平衡始终是架构师面临的核心挑战。MinIO作为高性能对象存储的代表,其纠删码(Erasure Coding,EC)机制提…...

Nanbeige 4.1-3B惊艳案例分享:学生用像素贤者终端完成编程作业与故事创作

Nanbeige 4.1-3B惊艳案例分享:学生用像素贤者终端完成编程作业与故事创作 1. 像素冒险终端的独特魅力 1.1 打破常规的交互体验 Nanbeige 4.1-3B像素冒险聊天终端彻底改变了传统AI对话界面的刻板印象。这款采用JRPG风格设计的终端,将枯燥的编程和写作任…...

智能旅行箱嵌入式系统设计:STM32多传感器融合与边缘智能实现

1. 项目概述1.1 设计动因与系统定位传统旅行箱长期停留在纯机械结构阶段,仅提供基础储运功能。在差旅频次提升、行李遗失风险加剧、机场限重政策趋严的现实背景下,用户对行李装备提出了三重刚性需求:物理防盗的可靠性、状态感知的实时性、人机…...

探索2024CUPT尺子把戏中的Comsol仿真模拟

2024cupt尺子把戏comsol仿真模拟在2024CUPT的众多有趣课题中,“尺子把戏”这一项目吸引了不少人的目光。借助Comsol这样强大的仿真模拟软件,我们可以深入剖析这一现象背后的物理原理。 “尺子把戏”现象简述 想象一把尺子,一端固定在桌面上&a…...

嵌入式C语言错误处理五大核心技术与工程实践

1. 嵌入式系统错误处理的工程实践体系嵌入式软件开发与通用计算平台存在本质差异:资源受限、实时性要求高、可靠性为第一优先级、缺乏完善的运行时环境支持。在裸机或轻量级RTOS环境下,C语言作为主流开发语言,其错误处理机制必须兼顾确定性、…...

Buck - Boost双向DC - DC电源学习资料大揭秘

Buck-Boost双向DC-DC电源整套学习资料 功能:采用STM32F334C8T6芯片,能够根据输入电压和输出电压的大小关系,实现自动切换工作模式,将参数信息进行显示,并且可以实现稳压输出。 程序仿真硬件软件说明报告原理图计算书等…...

基于Qt的轻量级串口调试助手设计与实现

1. 项目概述串口通信作为嵌入式系统中最基础、最可靠的物理层数据交互方式,至今仍是硬件调试、固件升级、传感器数据采集等场景的首选方案。在实际开发过程中,工程师需要频繁验证单片机与上位机之间的协议一致性、时序容错性及数据完整性。尽管市面上存在…...

达摩院PALM模型春联应用:春联生成模型-中文-base案例展示

达摩院PALM模型春联应用:春联生成模型-中文-base案例展示 1. 传统习俗遇上AI技术 春节贴春联是中国传统文化中不可或缺的一部分。一副好的春联不仅需要工整对仗、平仄协调,还要蕴含美好的祝福寓意。然而,创作一副既符合规范又富有新意的春联…...

ZYNQ平台AXI DMA传输避坑指南:如何快速定位‘errors:200‘等中断故障

ZYNQ平台AXI DMA故障诊断实战:从寄存器解析到压力测试的完整解决方案 1. 深入理解AXI DMA中断机制与错误分类 在ZYNQ平台上,AXI DMA作为PL与PS之间高速数据传输的核心引擎,其稳定性直接影响系统性能。但开发者常被突如其来的中断故障困扰&…...

游戏开发者必看:如何用FairGuard方案彻底防御Cheat Engine内存修改(附实战案例)

游戏安全防护实战:从原理到对抗Cheat Engine的完整解决方案 在游戏行业蓬勃发展的今天,安全问题已成为开发者面临的最大挑战之一。作为游戏开发者,我们投入大量心血打造的游戏世界,常常因为外挂工具的入侵而遭受破坏。其中&#x…...

Kubernetes上部署VASTBASE G100的实战教程:StatefulSet与持久化存储配置

Kubernetes上部署VASTBASE G100的实战教程:StatefulSet与持久化存储配置 在云原生技术席卷企业IT基础设施的今天,数据库作为核心业务组件,其部署方式正经历着从传统物理机到容器化编排的历史性转变。VASTBASE G100作为国产高性能数据库的代表…...

STM32 + MQTT 实战:从零构建工业级物联网设备通信框架

1. 为什么选择STM32MQTT构建工业物联网通信框架 第一次接触工业物联网项目时,我踩过一个典型的技术选型坑——用HTTP协议做设备通信。当时在某个环境监测项目中,设备每隔5秒上报一次温湿度数据,结果网络稍有波动就会导致数据堆积,…...

Qwen3-0.6B-FP8处理操作系统相关问答:从安装到故障排查

Qwen3-0.6B-FP8处理操作系统相关问答:从安装到故障排查 你有没有遇到过电脑突然蓝屏,屏幕上显示一堆看不懂的代码?或者想给电脑装个新系统,看着网上五花八门的教程却不知道从哪下手?又或者,某个软件突然打…...

Qwen2.5-7B-Instruct应用实战:智能客服、代码助手、创作伙伴搭建

Qwen2.5-7B-Instruct应用实战:智能客服、代码助手、创作伙伴搭建 1. 项目概述 Qwen2.5-7B-Instruct是阿里通义千问团队推出的旗舰级大语言模型,拥有70亿参数规模,在18T tokens数据上进行了预训练和指令微调。相比轻量级版本,7B参…...

libsodium-esphome:ESP32/ESP8266上的Noise协议轻量密码库

1. libsodium-esphome:面向ESPHome生态的轻量化密码学库移植1.1 项目定位与工程动因libsodium-esphome并非一个独立密码学实现,而是对成熟工业级密码库libsodium 1.0.18的精准裁剪与嵌入式适配。其核心目标明确:为 ESPHome 固件提供最小可行、…...

从零开始:DW_apb_uart的RS485模式配置与调试全流程

DW_apb_uart RS485工业通信实战:从寄存器配置到总线调试的深度解析 在工业自动化领域,RS485总线因其抗干扰能力强、传输距离远等优势,成为设备间通信的首选方案。DW_apb_uart作为一款高度可配置的通用异步收发器,其RS485模式支持为…...

CentOS 7单机伪集群部署DolphinScheduler 3.2.2:从零搭建可视化调度平台

1. 环境准备:打造DolphinScheduler的温床 在CentOS 7上部署DolphinScheduler伪集群,就像给新房子打地基。我遇到过不少初学者在环境配置阶段就翻车,最常见的就是JDK版本不对或者数据库权限没开。咱们先从最基础的开始,把地基打牢。…...

跨语言自动化:Qwen3-32B多语言支持在OpenClaw中的应用

跨语言自动化:Qwen3-32B多语言支持在OpenClaw中的应用 1. 为什么需要多语言自动化助手 作为一个经常需要处理多语言内容的开发者,我一直在寻找能够真正理解并执行混合语言指令的自动化工具。传统的自动化脚本往往只能处理单一语言场景,当遇…...

QPST进阶玩法:不刷全包也能升级系统!用引导文件单独写入vendor分区实测

QPST高阶应用:精准分区更新技术解析与实战指南 在Android设备维护领域,全量刷机包动辄数GB的体积常常让技术爱好者们头疼——尤其是当你只需要更新基带或驱动等特定组件时。传统线刷方式不仅耗时耗力,还存在用户数据丢失的风险。本文将深入探…...

脑影像预测新工具 | NBS-Predict:融合脑网络与机器学习的智能诊断方案

1. NBS-Predict是什么?为什么它值得关注? 想象你是一位神经科医生,每天要面对几十张复杂的大脑扫描影像。传统诊断就像在迷宫里摸黑前行——依赖经验、容易漏诊、耗时费力。而NBS-Predict就像给你装上了夜视仪导航仪的组合装备,它…...

VSCode远程开发Qwen3-ForcedAligner-0.6B:Linux服务器调试全攻略

VSCode远程开发Qwen3-ForcedAligner-0.6B:Linux服务器调试全攻略 用VSCode远程连接Linux服务器,让语音文本对齐开发变得简单高效 你是否曾经遇到过这样的情况:在本地电脑上开发语音处理应用,但硬件性能跟不上,跑个模型…...

本科毕业论文 AI 写作新范式:Paperzz 4 步智能写作系统,解锁毕业高效新体验

Paperzz-AI官网免费论文查重复率AIGC检测/开题报告/文献综述/论文初稿paperzz - 毕业论文-AIGC论文检测-AI智能降重-ai智能写作https://www.paperzz.cc/dissertation 一、本科毕业论文的写作困局与破局 本科毕业论文是大学学业的收官之作,却也是无数学生的 “毕业拦…...