Sharding-JDBC源码解析(三)SQL路由

Sharding-JDBC源码解析(一)整体流程-CSDN博客

Sharding-JDBC源码解析(二)SQL解析-CSDN博客

Sharding-JDBC源码解析(三)SQL路由-CSDN博客

Sharding-JDBC源码解析(四)SQL改写-CSDN博客

Sharding-JDBC源码解析(五)SQL执行-CSDN博客

Sharding-JDBC源码解析(六)结果归并-CSDN博客

目录

一、整体概述

二、详细流程

1.路由整体流程

2.分片路由器

 2-1.解析分片条件

 2-2.选择路由引擎

标准路由引擎

复杂路由引擎

单表路由引擎

广播路由引擎

单播路由引擎

3.主从路由器


一、整体概述

在ShardingShpere中,路由分为两种,即携带分片键的分片路由以及不携带分片键的广播路由,本编笔记仅针对分片路由进行解析。 

二、详细流程

下列的代码部分删减掉非核心的部分,部分直接以伪代码或文字的方式描述,需要详细的信息可以自行参照方法入口去阅读。

1.路由整体流程

首先路由的方法入口位于KernelProcessor#route方法,KernelProcessor中最终委托给PartialSQLRouteExecutor#route去完成路由。路由使用的是装饰器模式,也就是一条路由器链,将所有可用的数据节点作为输入,结合配置的规则和sql,经过这个路由器链的计算,最终会输出该条sql对应的数据节点列表,也就是所谓的路由单元。而具体一条sql需要经过哪些路由器,PartialSQLRouteExecutor会根据配置去加载对应的规则,比如根据配置该条sql既涉及到了分片又涉及到了主从,那么路由器链就由分片路由器 + 主从路由器组成,路由器链的顺序会依据每个路由器自身的优先级。

PartialSQLRouteExecutor#route方法如下

    public RouteContext route(final LogicSQL logicSQL, final ShardingSphereMetaData metaData) {
        RouteContext result = new RouteContext();
        // routers即为路由器链
        // 假设当前路由器链由分片路由器 + 主从路由器组成
        // 且存在一条SQL  select * from tb ,现在仅做了分库操作,逻辑数据库ds 对应的物理库为 ds0, ds1
        // 则经过分片路由器解析后得出2个路由单元ds0.tb, ds1.tb
        // 又因为当前的主从配置如下,也就是读操作指向从库
        // ds0 :  master -> ds0  slave -> ds0-slave 
        // ds1 :  master -> ds1  slave -> ds1-slave
        // 那么再经过主从路由器解析后,从ds0.tb, ds1.tb变成ds0-slave.tb,  ds1-slave.tb
        for (Map.Entry<ShardingSphereRule, SQLRouter> entry : routers.entrySet()) {
            if (result.getRouteUnits().isEmpty()) {
                result = entry.getValue().createRouteContext(logicSQL, metaData, entry.getKey(), props);
            } else {
                entry.getValue().decorateRouteContext(result, logicSQL, metaData, entry.getKey(), props);
            }
        }

        // 没有匹配到任何规则且当前的分片规则配置下,只存在一个物理数据源,会默认生成一个路由单元,指向唯一的数据源,也就是没有配置分表分库的情况。
        // 代码略...

        return result;
    }

2.分片路由器

在分片路由器中,首先会去解析sql中的分片条件,也就是说解析出一条sql中哪些条件可以作为分片的依据。然后根据sql的类型(dcl、dql、ddl、dal等)和查询的类型(标准查询、联合查询、复查查询等)加载对应的分片引擎,最终去解析需要发起请求的数据节点(即路由单元)

同样的此处举个例子,存在如下sql : select * from tb where id in (1,2,3,4) and age in (2,4,6,8),分表策略为标准分片(standard),分片键为id字段,分片算法的表达式为 tb${id % 2} ,那么此时解析出来的分片条件就是 tb.id in (2,4,6,8),且结合sql类型和查询类型此处会选择标准分片引擎,最终计算出需要请求的数据节点为为ds.tb1和db.tb0。

上述代码位于ShardingSQLRouter#createRouteContext

    public RouteContext createRouteContext(final LogicSQL logicSQL, final ShardingSphereMetaData metaData, final ShardingRule rule, final ConfigurationProperties props) {

        // 解析分片条件。
        ShardingConditions shardingConditions = createShardingConditions(logicSQL, metaData, rule);

        // 如果包含子查询,则合并这些分片条件。
        if (sqlStatement instanceof DMLStatement && shardingConditions.isNeedMerge()) {
            shardingConditions.merge();
        }
        // 选择分片引擎进行路由。
        RouteContext result = ShardingRouteEngineFactory.newInstance(rule, metaData, logicSQL.getSqlStatementContext(), shardingConditions, props).route(rule);

        return result;
    }
 2-1.解析分片条件
  • 读操作条件解析
    代码入口在WhereClauseShardingConditionEngine#createShardingConditions,主要作用就是从给定的where条件中解析出分片条件(即可以参与分片计算的条件)。
        public List<ShardingCondition> createShardingConditions(final SQLStatementContext<?> sqlStatementContext, final List<Object> parameters) {
            List<ShardingCondition> result = new ArrayList<>();
            // 获取where中的条件
            for (WhereSegment each : getWhereSegments(sqlStatementContext)) {
                // 从给定的where条件中解析出分片条件(即可以参与分片计算的条件)
                // 例如存在sql select * from tb where id in (1,2,3) and age in (3,4,5)
    ,且表tb的分片键配置为id,则此处最终解析出的分片条件为 tb.id in (1,2,3)。
    
                result.addAll(createShardingConditions(sqlStatementContext, each.getExpr(), parameters));
            }
            return result;
        }
  • 写操作条件解析
    代码入口在InsertClauseShardingConditionEngine#createShardingConditions,insert语句的条件解析分为两种,此处假设id为分片键,那么insert语句分为两种情况,一种指定了id,另外一种是使用数据库自增id, 指定id的情况下,通过解析sql中的id值生成分片条件, 没有指定id的情况下,会使用用户的自增策略生成id,再通过id去生成分片条件。
        public List<ShardingCondition> createShardingConditions(final InsertStatementContext sqlStatementContext, final List<Object> parameters) {
            // 指定id的情况下,即可以直接使用id生成分片条件
            List<ShardingCondition> result = null == sqlStatementContext.getInsertSelectContext()
                    ? createShardingConditionsWithInsertValues(sqlStatementContext, parameters) : createShardingConditionsWithInsertSelect(sqlStatementContext, parameters);
            // 没有指定id的情况下,会使用用户的自增策略生成id,再通过id去生成分片条件
            appendGeneratedKeyConditions(sqlStatementContext, result);
            return result;
        }
 2-2.选择路由引擎
  • 标准路由引擎

    在sql为DQL类型(即查询sql),且查询类型为标准查询时(即单表分片查询,或者连表但是涉及的表都为绑定表),会使用标准路由引擎进行路由。标准路由引擎会根据用户配置的分片策略(hint、standard、complex)结合分片条件解析出数据节点。

    代码入口位于ShardingStandardRoutingEngine#route

        public RouteContext route(final ShardingRule shardingRule) {
            RouteContext result = new RouteContext();
            // 结合具体使用的分片策略解析出数据节点(数据分片的最小单元,由数据源名称和真实表组成。 例:ds0.tb0)
            Collection<DataNode> dataNodes = getDataNodes(shardingRule, shardingRule.getTableRule(logicTableName));
            // dataNodes结构转换成路由单元  代码略..
            return result;
        }
    
    
        private Collection<DataNode> getDataNodes(final ShardingRule shardingRule, final TableRule tableRule) {
            // 获取分库、分库策略
            ShardingStrategy databaseShardingStrategy = createShardingStrategy(...);
            ShardingStrategy tableShardingStrategy = createShardingStrategy(...);
            // 根据配置的分片策略去执行 hint / standard / complex
            // 最终调用的方法都是route0方法
    
        }
         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);
            // 再遍历每个数据源 对表进行路由 
            // 也就是在routeDataSources方法中进行分库, 在routeTables方法中进行分表
    
            Collection<DataNode> result = new LinkedList<>();
            for (String each : routedDataSources) {
                result.addAll(routeTables(tableRule, each, tableShardingStrategy, tableShardingValues));
            }
            return result;
        }
    
    
    
        private Collection<String> routeDataSources(final TableRule tableRule, final ShardingStrategy databaseShardingStrategy, final List<ShardingConditionValue> databaseShardingValues) {
    
            // 根据传入的分库策略执行分片
    
            Collection<String> result = new LinkedHashSet<>(databaseShardingStrategy.doSharding(tableRule.getActualDatasourceNames(), databaseShardingValues, properties));
    
            return result;
        }
        
        private Collection<DataNode> routeTables(final TableRule tableRule, final String routedDataSource,
                                                 final ShardingStrategy tableShardingStrategy, final List<ShardingConditionValue> tableShardingValues) {
            Collection<String> availableTargetTables = tableRule.getActualTableNames(routedDataSource);
            // 如果不含分片条件 则返回所有的可用表 否则执行对应的分片策略
            Collection<String> routedTables = new LinkedHashSet<>(tableShardingValues.isEmpty()
                    ? availableTargetTables : tableShardingStrategy.doSharding(availableTargetTables, tableShardingValues, properties));
            // 转换成数据节点 代码略..
            return result;
        }        

    标准分片策略

    标准分片策略是官方最为推荐的分片方式,支持的>=, https://shardingsphere.apache.org/document/5.0.0/cn/user-manual/shardingsphere-jdbc/configuration/built-in-algorithm/sharding/#%E6%A0%87%E5%87%86%E5%88%86%E7%89%87%E7%AE%97%E6%B3%95

    标准分片策略的分片逻辑入口位于StandardShardingStrategy#doSharding 

        public Collection<String> doSharding(final Collection<String> availableTargetNames, final Collection<ShardingConditionValue> shardingConditionValues, final ConfigurationProperties props) {
            // 因为标准分片又分为行表达式分片算法和时间范围分片算法,此处仅解析行表达式分片算法
            // 执行时也分为两种情况。当sql为精准查询时(例如 =/in这些条件),会将分片条件带入行表达式计算出对应的数据节点。
            // 而如果sql为范围查询比如使用的是 >、<、BETWEEN AND等这些条件。那么会直接进行全路由,,默认情况下行表达式算法不开启范围查询
            // 可以通过allow-range-query-with-inline-sharding设定。
        }

    复杂分片策略

    可以简单理解为多分片键的标准分片策略。代码入口位于ComplexInlineShardingAlgorithm#doSharding

    Hint分片策略
    hint策略即在sql查询之前,用户通过HintManager在线程上下文中设置分片值,而后在HintInlineShardingAlgorithm#doSharding方法结合配置用的hint算法计算出最终的分片值

        public void query(){
            HintManager.clear();
            HintManager.getInstance().setDatabaseShardingValue("ds0");
            // 执行sql查询..
        }

  • 复杂路由引擎
     

    如果使用了连表查询,且表之间不全为绑定表的关系时,会使用复杂路由引擎。该引擎的主要流程即对每个逻辑表应用标准路由引擎,记录每张表的路由结果,而后使用笛卡尔积路由生成最终的结果。

    代码位于ShardingComplexRoutingEngine#route

        public RouteContext route(final ShardingRule shardingRule) {
            for (String each : logicTables) {
                Optional<TableRule> tableRule = shardingRule.findTableRule(each);
                if (tableRule.isPresent()) {
                    // 使用标准路由引擎路由
                    if (!bindingTableNames.contains(each)) {
                        routeContexts.add(new ShardingStandardRoutingEngine(tableRule.get().getLogicTable(), shardingConditions, props).route(shardingRule));
                    }
                    shardingRule.findBindingTableRule(each).ifPresent(bindingTableRule -> bindingTableNames.addAll(bindingTableRule.getTableRules().keySet()));
                }
            }
            // 省略部分分支...
            // 对多个路由结果进行笛卡尔积
            RouteContext routeContext = new ShardingCartesianRoutingEngine(routeContexts).route(shardingRule);
            result.getOriginalDataNodes().addAll(routeContext.getOriginalDataNodes());
            result.getRouteUnits().addAll(routeContext.getRouteUnits());
            return result;
        }

  • 单表路由引擎

    对于那些没有配置规则的表,会使用SingleTableRouteEngine去路由,在这个引擎中,查询只会走固定某个数据源,至于数据源要如何确定,则是按照数据源配置的顺序去选择。举个例子,比如表user无任何规则,且数据源配置了ds0,ds1,则优先使用ds0,如果ds0不存在表user,则依次后推。

    方法位于SingleTableRouteEngine#route

  • 广播路由引擎

    当某个表配置为广播表(广播表的概念即每个数据源中都有该表,适用于一些体量较小的表,比如字典表)。那么该表的写请求会由广播路由引擎路由到所有数据源。

    ShardingDatabaseBroadcastRoutingEngine#route

  • 单播路由引擎

    广播表的读请求由单播路由引擎来进行路由,由于所有数据源上都有该表,所以该引擎会将读请求随机路由到某个节点。

    ShardingUnicastRoutingEngine#route

3.主从路由器

TODO..

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值