目标
继续本系列的第3篇分析文章:分析【路由引擎】的源码。
在上一篇文章里,通过分析【解析引擎】的源码,我们了解到解析后得到的sqlStatement里,包含了提炼分片所需的上下文信息,也就是为下一步的【路由引擎】服务。
那么,【路由引擎】具体是如何工作的呢?通过分析源码,我们试图去找出答案
查阅文档,快速入门
首先,通过查阅官方文档,我们了解到ShardingSphere的多种路由策略,以及匹配的场景,一图以蔽之:
概念解释
在官方介绍中,出现了很多专业术语,如:分片键、分片算法、Hint、强制分片路由等,初学者一定要先搞清这些概念,才能更好地理解【路由引擎】的工作原理。
上述的术语解释,参照:shardingsphere.apache.org/document/cu…
另外的:
绑定表:
指分片规则一致的主表和子表。
例如: t_order 表和 t_order_item 表,均按照 order_id 分片,绑定表之间的分区键完全相同,则此两张表互为绑定表关系。
绑定表之间的多表关联查询不会出现笛卡尔积关联,关联查询效率将大大提升。
解析源码
经过上一步,我们了解到:【路由引擎】是根据解析上下文匹配数据库和表的分片策略,并生成路由路径。
接下来,我们通过分析源码,来找出【路由引擎】的工作过程:
// 选取入口,打上断点,执行路由后,查看返回的路径信息
public ExecutionContext generateExecutionContext(final LogicSQL logicSQL, final ShardingSphereMetaData metaData, final ConfigurationProperties props) {
// 路由
RouteContext routeContext = route(logicSQL, metaData, props);
// 改写
SQLRewriteResult rewriteResult = rewrite(logicSQL, metaData, props, routeContext);
// 执行
ExecutionContext result = createExecutionContext(logicSQL, metaData, routeContext, rewriteResult);
// 日志打印
logSQL(logicSQL, props, result);
return result;
}
1. 获取执行器,开始执行
public RouteContext route(final LogicSQL logicSQL, final ShardingSphereMetaData metaData) {
// 根据sqlStatement判断:用全路由执行器or部分路由执行器,条件为:sqlStatement instanceof MySQLShowTablesStatement?
SQLRouteExecutor executor = isNeedAllSchemas(logicSQL.getSqlStatementContext().getSqlStatement()) ? new AllSQLRouteExecutor() : new PartialSQLRouteExecutor(rules, props);
return executor.route(logicSQL, metaData);
}
2. 获取配置的分片规则后,进一步验证、合并
public RouteContext createRouteContext(final LogicSQL logicSQL, final ShardingSphereMetaData metaData, final ShardingRule rule, final ConfigurationProperties props) {
RouteContext result = new RouteContext();
SQLStatement sqlStatement = logicSQL.getSqlStatementContext().getSqlStatement();
// 拿到分片规则
ShardingConditions shardingConditions = createShardingConditions(logicSQL, metaData, rule);
// 验证器验证分片规则
Optional<ShardingStatementValidator> validator = ShardingStatementValidatorFactory.newInstance(sqlStatement, shardingConditions);
validator.ifPresent(v -> v.preValidate(rule, logicSQL.getSqlStatementContext(), logicSQL.getParameters(), metaData.getSchema()));
/*
* 判断当前的分片规则是否需要合并
* 合并条件:
* 1、DML语句
* 2、是查询语句且包含子查询
*/
if (sqlStatement instanceof DMLStatement && shardingConditions.isNeedMerge()) {
// 只保留最后一个规则
shardingConditions.merge();
}
// 获取对应的策略后,执行路由策略,并把结果写入result变量
ShardingRouteEngineFactory.newInstance(rule, metaData, logicSQL.getSqlStatementContext(), shardingConditions, props).route(result, rule);
validator.ifPresent(v -> v.postValidate(rule, logicSQL.getSqlStatementContext(), result, metaData.getSchema()));
return result;
}
3. 执行分片规则
public void route(final RouteContext routeContext, final ShardingRule shardingRule) {
/**
* DataNode:路由后的路径信息,包括DB、table
*/
Collection<DataNode> dataNodes = getDataNodes(shardingRule, shardingRule.getTableRule(logicTableName));
routeContext.getOriginalDataNodes().addAll(originalDataNodes);
for (DataNode each : dataNodes) {
// 遍历返回的数据节点,加到【路由上下文】里
routeContext.getRouteUnits().add(
new RouteUnit(new RouteMapper(each.getDataSourceName(), each.getDataSourceName()), Collections.singleton(new RouteMapper(logicTableName, each.getTableName()))));
}
}
3.1. 获取分库规则、分表规则
private Collection<DataNode> getDataNodes(final ShardingRule shardingRule, final TableRule tableRule) {
// 分库策略
ShardingStrategy databaseShardingStrategy = createShardingStrategy(shardingRule.getDatabaseShardingStrategyConfiguration(tableRule),
shardingRule.getShardingAlgorithms(), shardingRule.getDefaultShardingColumn());
// 分表策略
ShardingStrategy tableShardingStrategy = createShardingStrategy(shardingRule.getTableShardingStrategyConfiguration(tableRule),
shardingRule.getShardingAlgorithms(), shardingRule.getDefaultShardingColumn());
// 是否走强制分片策略:库跟表都是hint(优先级最高)
if (isRoutingByHint(shardingRule, tableRule)) {
return routeByHint(tableRule, databaseShardingStrategy, tableShardingStrategy);
}
// 是否走标准路由,库跟表都不是hint
if (isRoutingByShardingConditions(shardingRule, tableRule)) {
return routeByShardingConditions(shardingRule, tableRule, databaseShardingStrategy, tableShardingStrategy);
}
return routeByMixedConditions(shardingRule, tableRule, databaseShardingStrategy, tableShardingStrategy);
}
3.2. 判断是否直接路由
private boolean isRoutingByHint(final ShardingRule shardingRule, final TableRule tableRule) {
// 配置了hint策略
return shardingRule.getDatabaseShardingStrategyConfiguration(tableRule) instanceof HintShardingStrategyConfiguration
&& shardingRule.getTableShardingStrategyConfiguration(tableRule) instanceof HintShardingStrategyConfiguration;
}
3.3. 先执行分库,再执行分表
private Collection<DataNode> route0(final TableRule tableRule,
final ShardingStrategy databaseShardingStrategy, final List<ShardingConditionValue> databaseShardingValues,
final ShardingStrategy tableShardingStrategy, final List<ShardingConditionValue> tableShardingValues) {
// 先执行数据库的路由策略,得到结果
Collection<String> routedDataSources = routeDataSources(tableRule, databaseShardingStrategy, databaseShardingValues);
Collection<DataNode> result = new LinkedList<>();
for (String each : routedDataSources) {
// 再遍历所有数据库,执行表的路由策略,得到最终结果
result.addAll(routeTables(tableRule, each, tableShardingStrategy, tableShardingValues));
}
return result;
}
private Collection<String> doSharding(final Collection<String> availableTargetNames, final ListShardingConditionValue<?> shardingValue) {
Collection<String> result = new LinkedList<>();
for (Comparable<?> each : shardingValue.getValues()) {
String target;
// 执行完配置的分片算法后,得到分片结果
target = shardingAlgorithm.doSharding(availableTargetNames, new PreciseShardingValue(shardingValue.getTableName(), shardingValue.getColumnName(), each));
if (null != target && availableTargetNames.contains(target)) {
result.add(target);
} else if (null != target && !availableTargetNames.contains(target)) {
throw new ShardingSphereException(String.format("Route table %s does not exist, available actual table: %s", target, availableTargetNames));
}
}
return result;
}
执行到这一步,我们打上断点,查看一下分片算法和分片后的路径信息:
可以看到,分片的路径是符合预期的。
3.4. 封装结果返回
4. 得到最终的路由路径
流程图
总结
一言以蔽之,【路由引擎】是通过【解析上下文】匹配对应的数据库和表的分片策略,来生成路由路径。