路由引擎是整个分片引擎执行流程中的第二步,即基于 SQL 解析引擎所生成的 SQLStatement,通过解析执行过程中所携带的上下文信息,来获取匹配数据库和表的分片策略,并生成路由结果。

工程结构

类结构

SharedPreferences源码 shardingsphere源码_SQL

sharding-core-route 工程

注意前一个流程的输入和当前流程应该的输出。
ShardingRouter类,该类是整个路由流程的启动点。ShardingRouter 类直接依赖于解析引擎 SQLParseEngine 类完成 SQL 解析并获取SQLStatement 对象,然后供PreparedStatementRoutingEngine 和 StatementRoutingEngine 进行使用。

sharding-core-entry 工程

另一方面,上图中的 PreparedQueryShardingEngine 和 SimpleQueryShardingEngine 则位于 sharding-core-entry 工程中。从包的命名上看,entry 相当于是访问的入口,所以我们可以判断这个工程中所提供的类属于面向应用层组件,处于更加上层的位置。PreparedQueryShardingEngine 和 SimpleQueryShardingEngine 的使用者分别是 ShardingPreparedStatement 和 ShardingStatement。这两个类再往上就是 ShardingConnection 以及 ShardingDataSource 这些直接面向应用层的类了。

路由核心类:ShardingRouter

下面重点说下路由核心类。在 ShardingRouter 中,我们首先看到了熟悉的 SQL 解析引擎 SQLParseEngine 以及它的使用方法:

public SQLStatement parse(final String logicSQL, final boolean useCache) { 
        return parseEngine.parse(logicSQL, useCache); 
}

上述代码即通过 SQLParseEngine 对传入的 SQL 进行解析返回一个 SQLStatement 对象。这里将 SQL 命名为 logicSQL,以便区别在分片和读写分离情况下的真实 SQL。

ShardingRouter 类中有个核心方法route方法。

SharedPreferences源码 shardingsphere源码_sql_02


ShardingRouter 是路由引擎的核心类,在接下来的内容中,我们将对上图中的 6 个步骤分别一 一 详细展开,帮忙你理解一个路由引擎的设计思想和实现机制。

  1. 分片合理性验证,如验证分片操作是否支持。
public final class ShardingInsertStatementValidator implements ShardingStatementValidator<InsertStatement> { 

    @Override 
    public void validate(final ShardingRule shardingRule, final InsertStatement sqlStatement, final List<Object> parameters) { 
        Optional<OnDuplicateKeyColumnsSegment> 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”语法。

  1. 获取上下文
    route方法的第二段代码用于获取运行时的 SQLStatement 上下文。可以看到这里构建了上下文对象 SQLStatementContext,同样用到了工厂模式,工厂类 SQLStatementContextFactory 如下所示:
public final class SQLStatementContextFactory { 

    public static SQLStatementContext newInstance(final RelationMetas relationMetas, final String sql, final List<Object> 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); 
    } 
}

对于 SelectSQLStatement,通常也需要保存与查询相关的分组上下文 GroupByContext、排序上下文 OrderByContext 和分页上下文 PaginationContext;而对于InsertSQLStatementContext 而言,InsertValueContext 则包含了所有与插入操作相关的值对象。

  1. 自动生成主键
    这段代码的逻辑比较明确,即如果输入的 SQLStatement 是 InsertStatement,则自动创建一个主键 GeneratedKey,反之就不做处理。
//如果是 InsertStatement 则自动生成主键 
Optional<GeneratedKey> generatedKey = sqlStatement instanceof InsertStatement 
            ? GeneratedKey.getGenerateKey(shardingRule, metaData.getTables(), parameters, (InsertStatement) sqlStatement) : Optional.<GeneratedKey>absent();
  1. 创建分片条件
    我们来看 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); 
}

下面看下节点信息和路由信息。

public class ShardingCondition { 
    //路由信息 
    private final List<RouteValue> routeValues = new LinkedList<>(); 
    //节点信息 
    private final Collection<DataNode> dataNodes = new LinkedList<>(); 
}

那么如何获取分片条件呢?如下所示的 getShardingConditions 方法给出了具体的实现方式,可以看到这里根据输入的 SQL 类型,分别通过 InsertClauseShardingConditionEngine 和WhereClauseShardingConditionEngine 创建了 ShardingConditions:

private ShardingConditions getShardingConditions(final List<Object> 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.<ShardingCondition>emptyList()); 
}

对于路由引擎而言,分片条件的主要目的就是提取用于路由的目标数据库、表和列之间的关系,InsertClauseShardingConditionEngine 和 WhereClauseShardingConditionEngine 中的处理逻辑都是为了构建包含这些关系信息的一组 ShardingCondition 对象。

当获取这些 ShardingCondition 之后,我们还看到有一个优化的步骤,即调用mergeShardingConditions,对可以合并的 ShardingCondition 进行合并。

  1. 执行路由
    当我们获取了 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<String> 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); 
}
  1. 构建路由结果
    当通过一系列的路由引擎处理之后,我们获得了 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;

路由规则类:ShardingRule

ShardingRule代表着分片的各种规则信息。ShardingRule 类位于 sharding-core-common 工程中,主要保存着与分片相关的各种规则信息,以及 ShardingKeyGenerator 等分布式主键的创建过程,各个变量定义以及对应的注释如下所示:

//分片规则配置类,封装各种配置项信息 
private final ShardingRuleConfiguration ruleConfiguration; 
//DataSource 名称列表 
private final ShardingDataSourceNames shardingDataSourceNames; 
//针对表的规则列表 
private final Collection<TableRule> tableRules; 
//针对绑定表的规则列表 
private final Collection<BindingTableRule> bindingTableRules; 
//广播表名称列表 
private final Collection<String> broadcastTables; 
//默认的数据库分片策略 
private final ShardingStrategy defaultDatabaseShardingStrategy; 
//默认的数据表分片策略 
private final ShardingStrategy defaultTableShardingStrategy; 
//默认的分片键生成器 
private final ShardingKeyGenerator defaultShardingKeyGenerator; 
//针对读写分离的规则列表 
private final Collection<MasterSlaveRule> masterSlaveRules; 
//加密规则 
private final EncryptRule encryptRule;