shardingsphere源码分析(三)-- 路由引擎
- shardingsphere源码分析(三)-- 路由引擎
-
- 官方介绍
-
- 分片路由
- 广播路由
- debug
-
- 建表语句
- insert语句
- select语句
- 删除语句
- 总结
shardingsphere源码分析(三)-- 路由引擎
官方介绍
链接如下:
https://shardingsphere.apache.org/document/current/cn/features/sharding/principle/route/
根据解析上下文匹配数据库和表的分片策略,并生成路由路径。 对于携带分片键的 SQL,根据分片键的不同可以划分为单片路由(分片键的操作符是等号)、多片路由(分片键的操作符是 IN)和范围路由(分片键的操作符是 BETWEEN)。 不携带分片键的 SQL 则采用广播路由。
分片路由
用于根据分片键进行路由的场景,又细分为直接路由、标准路由和笛卡尔积路由这 3 种类型。
-
直接路由
满足直接路由的条件相对苛刻,它需要通过 Hint(使用 HintAPI 直接指定路由至库表)方式分片,并且是只分库不分表的前提下,则可以避免 SQL 解析和之后的结果归并。 因此它的兼容性最好,可以执行包括子查询、自定义函数等复杂情况的任意 SQL。直接路由还可以用于分片键不在 SQL 中的场景。
-
标准路由
标准路由是 ShardingSphere 最为推荐使用的分片方式,它的适用范围是不包含关联查询或仅包含绑定表之间关联查询的 SQL。 当分片运算符是等于号时,路由结果将落入单库(表),当分片运算符是 BETWEEN 或 IN 时,则路由结果不一定落入唯一的库(表),因此一条逻辑 SQL 最终可能被拆分为多条用于执行的真实 SQL。
-
笛卡尔路由
笛卡尔路由是最复杂的情况,它无法根据绑定表的关系定位分片规则,因此非绑定表之间的关联查询需要拆解为笛卡尔积组合执行。
广播路由
对于不携带分片键的 SQL,则采取广播路由的方式。根据 SQL 类型又可以划分为全库表路由、全库路由、全实例路由、单播路由和阻断路由这 5 种类型。
-
全库表路由
全库表路由用于处理对数据库中与其逻辑表相关的所有真实表的操作,主要包括不带分片键的 DQL 和 DML,以及 DDL 等。
-
全库路由
全库路由用于处理对数据库的操作,包括用于库设置的 SET 类型的数据库管理命令,以及 TCL 这样的事务控制语句。 在这种情况下,会根据逻辑库的名字遍历所有符合名字匹配的真实库,并在真实库中执行该命令,
-
全实例路由
全实例路由用于 DCL 操作,授权语句针对的是数据库的实例。无论一个实例中包含多少个 Schema,每个数据库的实例只执行一次。
-
单播路由
单播路由用于获取某一真实表信息的场景,它仅需要从任意库中的任意真实表中获取数据即可。
-
阻断路由
阻断路由用于屏蔽 SQL 对数据库的操作
debug
我们继续运行 examples/shardingsphere-jdbc-example/sharding-example/sharding-raw-jdbc-example/src/main/java/org/apache/shardingsphere/example/sharding/raw/jdbc/YamlRangeConfigurationExampleMain.java
建表语句
接着上一篇文章最后的那段代码
// ShardingSphereStatement.java
private ExecutionContext createExecutionContext(String sql) throws SQLException {
...
// 后面就是路由和改写了
return this.kernelProcessor.generateExecutionContext(logicSQL, this.metaDataContexts.getDefaultMetaData(), this.metaDataContexts.getProps());
}
// KernelProcessor.java
public ExecutionContext generateExecutionContext(LogicSQL logicSQL, ShardingSphereMetaData metaData, ConfigurationProperties props) {
// 路由
RouteContext routeContext = this.route(logicSQL, metaData, props);
SQLRewriteResult rewriteResult = this.rewrite(logicSQL, metaData, props, routeContext);
ExecutionContext result = this.createExecutionContext(logicSQL, metaData, routeContext, rewriteResult);
this.logSQL(logicSQL, props, result);
return result;
}
会根据元数据和配置新建路由引擎,然后调用路由函数
// SQLRouteEngine.java
public RouteContext route(LogicSQL logicSQL, ShardingSphereMetaData metaData) {
// 这里是一个三元表达式,判断是全路由还是部分路由
SQLRouteExecutor executor = this.isNeedAllSchemas(logicSQL.getSqlStatementContext().getSqlStatement()) ? new AllSQLRouteExecutor() : new PartialSQLRouteExecutor(this.rules, this.props);
return ((SQLRouteExecutor)executor).route(logicSQL, metaData);
}
建表语句,是走的 PartialSQLRouteExecutor
PartialSQLRouteExecutor会通过SPI的形式获取到配置的分库分表规则
public PartialSQLRouteExecutor(Collection<ShardingSphereRule> rules, ConfigurationProperties props) {
this.props = props;
this.routers = OrderedSPIRegistry.getRegisteredServices(rules, SQLRouter.class);
}
然后我们是用的分表的配置,所以后面走到了ShardingSQLRouter
public RouteContext createRouteContext(LogicSQL logicSQL, ShardingSphereMetaData metaData, ShardingRule rule, ConfigurationProperties props) {
RouteContext result = new RouteContext();
SQLStatement sqlStatement = logicSQL.getSqlStatementContext().getSqlStatement();
// 这里会验证sql是不是DDL语句或DML语句,不是的话,返回empty
Optional<ShardingStatementValidator> validator = ShardingStatementValidatorFactory.newInstance(sqlStatement);
// 这里会去验证语句,比如建表语句是验证创建的表是否存在,表存在的话会返回表已存在异常。
validator.ifPresent((optional) -> {
optional.preValidate(rule, logicSQL.getSqlStatementContext(), logicSQL.getParameters(), metaData.getSchema());
});
// 判断分表的条件,DML语句的话会加条件
ShardingConditions shardingConditions = this.createShardingConditions(logicSQL, metaData, rule);
// 这里判断sql结果需不需要归并,建表语句不需要
boolean needMergeShardingValues = this.isNeedMergeShardingValues(logicSQL.getSqlStatementContext(), rule);
if (sqlStatement instanceof DMLStatement && needMergeShardingValues) {
this.mergeShardingConditions(shardingConditions);
}
//
ShardingRouteEngineFactory.newInstance(rule, metaData, logicSQL.getSqlStatementContext(), shardingConditions, props).route(result, rule);
// 校验数据
validator.ifPresent((v) -> {
v.postValidate(rule, logicSQL.getSqlStatementContext(), result, metaData.getSchema());
});
return result;
}
ShardingCartesianRoutingEngine.java
最后生成的路由是下面这样的,一个建表语句,会根据分表规则分别生成对应数据库的建表sql。
insert语句
然后我们是用的分表的配置,所以后面走到了ShardingSQLRouter
public RouteContext createRouteContext(LogicSQL logicSQL, ShardingSphereMetaData metaData, ShardingRule rule, ConfigurationProperties props) {
...
// 这里走的是标准路由,当分片运算符是等于号时,路由结果将落入单库(表)
ShardingRouteEngineFactory.newInstance(rule, metaData, logicSQL.getSqlStatementContext(), shardingConditions, props).route(result, rule);
}
// 最后路由到单库单表上
// ShardingDatabaseBroadcastRoutingEngine.java
public void route(RouteContext routeContext, ShardingRule shardingRule) {
Iterator var3 = shardingRule.getDataSourceNames().iterator();
while(var3.hasNext()) {
String each = (String)var3.next();
routeContext.getRouteUnits().add(new RouteUnit(new RouteMapper(each, each), Collections.emptyList()));
}
}
user_id=3,根据配置的路由规则,最后路由到了ds_1库的t_order表
select语句
// ShardingStandardRoutingEngine.java
public void route(RouteContext routeContext, ShardingRule shardingRule) {
// 根据逻辑表明获取有几个数据节点
Collection<DataNode> dataNodes = this.getDataNodes(shardingRule, shardingRule.getTableRule(this.logicTableName));
routeContext.getOriginalDataNodes().addAll(this.originalDataNodes);
Iterator var4 = dataNodes.iterator();
while(var4.hasNext()) {
DataNode each = (DataNode)var4.next();
routeContext.getRouteUnits().add(new RouteUnit(new RouteMapper(each.getDataSourceName(), each.getDataSourceName()), Collections.singleton(new RouteMapper(this.logicTableName, each.getTableName()))));
}
}
select * from t_order,由于没有带上分库键user_id,最后执行出来的RouteContext里包含了两个数据库的信息
删除语句
同样由于删除语句没有带上分库键user_id,所以路由到的是两个数据库
总结
为了提高性能,sql语句最好都带上分库键、分表键,然后就会全库全表查询。