ShardingSphere源码解析之路由引擎(一)

前面我们花了几篇文章对ShardingSphere中的SQL解析引擎做了介绍,我们明白SQL解析的作用就是根据输入的SQL语句生成一个SQLStatement对象。

从今天开始,我们将进入ShardingSphere的路由(Router)引擎部分的源码解析。从流程上讲,路由是整个SQL执行的第二步,即基于SQL解析引擎所生成的SQLStatement,然后根据解析上下文匹配数据库和表的分片策略,并生成路由路径。

在ShardingSphere中根据是否携带分片键可以将路由分成两大类型,即分片路由和广播路由。而分片路由又存在三种路由类型,即直接路由、标准路由和笛卡尔路由。对于不携带分片键的SQL,则采取广播路由的方式,根据SQL类型又可以划分为全库表路由、全库路由、全实例路由、单播路由和阻断路由这5种类型。关于这些路由类型的介绍不是我们的重点,大家可以参考ShardingSphere的官方说明(https://shardingsphere.apache.org/document/current/cn/features/sharding/principle/route/)。我们在后续的讲解中都会涉及到这些路由类型。

与介绍SQL解析引擎时一样,我们通过翻阅ShardingSphere源码,首先梳理了如下所示的类图:

上述类图总结了与路由机制相关的各个核心类,我们可以看到整体呈一种对称结构,即根据是普通Statement还是PreparedStatement分成两个分支流程,而这些流程的启动是ShardingRouter类,该类直接依赖于SQLParseEngine类完成SQL解析并获取SQLStatement对象,然后供PreparedStatementRoutingEngine和StatementRoutingEngine进行使用。这几个类都位于sharding-core-route工程中,处于流程的低端。而BaseShardingEngine及其它们的子类PreparedQueryShardingEngine和SimpleQueryShardingEngine则位于sharding-core-entry工程中,处于更加上层的位置。PreparedQueryShardingEngine和SimpleQueryShardingEngine的使用者分别是ShardingPreparedStatement和ShardingStatement,这两个类再往上就是ShardingConnection以及ShardingDataSource这些直接面向应用层的类了。

通过以上分析,我们首先对路由引擎这块的整体结构有了一个初步的认识。接下来,我们的思路还是从底层出发逐层往上分析流程的链路,先来看路由引擎中最底层的对象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(这里命名为logicSQL,区别于在分片和读写分离情况下的真实SQL)进行解析返回一个SQLStatement对象。

接下来我们来看一下ShardingRule,请注意这是一个基础类,该类位于sharding-core-common工程的org.apache.shardingsphere.core.rule包中,并继承了BaseRule接口,该接口如下所示:

public interface BaseRule {  

    RuleConfiguration getRuleConfiguration();

}

BaseRule的作用是获取规则配置,在ShardingSphere中,存在一批BaseRule接口的实现类,类层结构如下所示:      

而ShardingRule作为BaseRule接口的实现,主要保存着与分片相关的各种规则信息,以及ShardingKeyGenerator等分布式主键的创建过程,其变量定义如下所示:

    private final ShardingRuleConfiguration ruleConfiguration;   

    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;

ShardingRule的内容非常丰富,但其定位更多是提供规则信息而不属于核心流程,因此我们先不对其做详细展开,而是在后续讲解过程中穿插对它的介绍,这里我们先对它的变量有个简单认识即可。

在ShardingRouter类中,核心方法只有一个,即route方法。这个方法的逻辑比较复杂,其核心步骤如下图所示:

这个图值得我们花几篇文章进行详细讨论。针对上图,我们将该方法的代码分段进行介绍,首先是第一段代码:

//使用ShardingStatementValidatorStatement进行验证

    Optional<ShardingStatementValidator> shardingStatementValidator = ShardingStatementValidatorFactory.newInstance(sqlStatement);

        if (shardingStatementValidator.isPresent()) {

            shardingStatementValidator.get().validate(shardingRule, sqlStatement, parameters);

        }

这段代码使用ShardingStatementValidator对输入的SQLStatement进行验证,可以看到这里用到了典型的工厂模式,工厂类ShardingStatementValidatorFactory如下所示:

public final class ShardingStatementValidatorFactory {   

    public static Optional<ShardingStatementValidator> newInstance(final SQLStatement sqlStatement) {

        if (sqlStatement instanceof InsertStatement) {

            return Optional.<ShardingStatementValidator>of(new ShardingInsertStatementValidator());

        }

        if (sqlStatement instanceof UpdateStatement) {

            return Optional.<ShardingStatementValidator>of(new ShardingUpdateStatementValidator());

        }

        return Optional.absent();

    }

}

可以看到ShardingStatementValidator要验证的只有InsertStatement和UpdateStatement这两个SQLStatement。那么如何进行验证呢?我们来看一下ShardingStatementValidator的定义,如下所示:

public interface ShardingStatementValidator<T extends SQLStatement> {   

    //验证分片操作是否支持

    void validate(ShardingRule shardingRule, T sqlStatement, List<Object> parameters);

}

我们以ShardingInsertStatementValidator为例来看验证的方法,它的validate方法如下所示:

    @Override

    public void validate(final ShardingRule shardingRule, final InsertStatement sqlStatement, final List<Object> parameters) {

        Optional<OnDuplicateKeyColumnsSegment> onDuplicateKeyColumnsSegment = sqlStatement.findSQLSegment(OnDuplicateKeyColumnsSegment.class);

        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"语法。

接下来我们来看ShardingRouter类中route方法的第二段代码,如下所示:

//获取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<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);

    }

}

请注意,SQLStatementContext只有三种,即SelectSQLStatementContext、InsertSQLStatementContext以及CommonSQLStatementContext,它们都实现了SQLStatementContext接口,该接口如下所示:

public interface SQLStatementContext {   

    SQLStatement getSqlStatement();   

    TablesContext getTablesContext();

}

顾名思义,所谓的SQLStatementContext就是一种上下文对象,保存着与特定SQLStatement(如SelectSQLStatement和InsertSQLStatement)相关的上下文信息,用于为后续处理提供数据存储和传递的手段。

接下来的第三段代码与数据库主键相关,如下所示:

//如果是InsertStatement则自动生成主键

    Optional<GeneratedKey> generatedKey = sqlStatement instanceof InsertStatement

                ? GeneratedKey.getGenerateKey(shardingRule, metaData.getTables(), parameters, (InsertStatement) sqlStatement) : Optional.<GeneratedKey>absent();

这段代码的逻辑比较明确,即如果输入的SQLStatement是InsertStatement,则自动创建一个主键GeneratedKey,反之就不做处理。在数据分片的场景下,创建一个分布式主键实际上并没有那么简单,所以在这段代码背后有很多设计的思想和实现的技巧值得我们进行深入分析,我们会有一个专门的主题来剖析ShardingSphere中的分布式主键生成机制。

今天的内容就先讲到这里,我们分析了ShardingSphere中与路由引擎相关的核心结构,然后针对底层的ShardingRouter我们分析了它的代码结构并给出了部分代码块的解析。在下一篇中,我们会对ShardingRouter中剩余的代码块进行解析。

更多内容可以关注我的公众号:程序员向架构师转型。

发布了112 篇原创文章 · 获赞 12 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/lantian08251/article/details/104550484
今日推荐