Mycat路由

路由接口

io.mycat.route.RouteService
方法:

RouteResultset route(SystemConfig sysconf, SchemaConfig schema,
            int sqlType, String stmt, String charset, ServerConnection sc)

计算流程概述

image.png
image.png

conditions 为<表名,字段名,字段值> 3元组

单表计算流程


image.png

代码分析

AbstractRouteStrategy

    @Override
    public RouteResultset route(SystemConfig sysConfig, SchemaConfig schema, int sqlType, String origSQL,
            String charset, ServerConnection sc, LayerCachePool cachePool) throws SQLNonTransientException {
        //对应schema标签checkSQLschema属性,把表示schema的字符去掉
        if (schema.isCheckSQLSchema()) {
            origSQL = RouterUtil.removeSchema(origSQL, schema.getName());
        }

        /**
     * 处理一些路由之前的逻辑
     * 全局序列号,父子表插入
     */
        if ( beforeRouteProcess(schema, sqlType, origSQL, sc) ) {
            return null;
        }

        /**
         * SQL 语句拦截
         */
        String stmt = MycatServer.getInstance().getSqlInterceptor().interceptSQL(origSQL, sqlType);
        if (!origSQL.equals(stmt) && LOGGER.isDebugEnabled()) {
            LOGGER.debug("sql intercepted to " + stmt + " from " + origSQL);
        }
        RouteResultset rrs = new RouteResultset(stmt, sqlType);

        /**
         * 优化debug loaddata输出cache的日志会极大降低性能
         */
        if (LOGGER.isDebugEnabled() && origSQL.startsWith(LoadData.loadDataHint)) {
            rrs.setCacheAble(false);
        }
        /**
         * rrs携带ServerConnection的autocommit状态用于在sql解析的时候遇到
         * select ... for update的时候动态设定RouteResultsetNode的canRunInReadDB属性
         */
        if (sc != null ) {
            rrs.setAutocommit(sc.isAutocommit());
        }
        /**
         * DDL 语句的路由
         */
        if (ServerParse.DDL == sqlType) {
            return RouterUtil.routeToDDLNode(rrs, sqlType, stmt, schema);
        }

        /**
         * 检查是否有分片
         */
        if (schema.isNoSharding() && ServerParse.SHOW != sqlType) {
            rrs = RouterUtil.routeToSingleNode(rrs, schema.getDataNode(), stmt);
        } else {
            RouteResultset returnedSet = routeSystemInfo(schema, sqlType, stmt, rrs);
            if (returnedSet == null) {
                rrs = routeNormalSqlWithAST(schema, stmt, rrs, charset, cachePool,sqlType,sc);
            }
        }
        return rrs;
    }

从上面代码获知路由计算是在方法 routeNormalSqlWithAST中,这个是个抽象方法由子类来实现,目前是DruidMycatRouteStrategy

DruidMycatRouteStrategy.routeNormalSqlWithAST 路由是通过以下方法进行的

/**
     *  直接结果路由
     */
    private RouteResultset directRoute(RouteResultset rrs,DruidShardingParseInfo ctx,SchemaConfig schema,
                                        DruidParser druidParser,SQLStatement statement,LayerCachePool cachePool) throws SQLNonTransientException{
        
        //改写sql:如insert语句主键自增长, 在直接结果路由的情况下,进行sql 改写处理
        druidParser.changeSql(schema, rrs, statement,cachePool);
        /**
         * DruidParser 解析过程中已完成了路由的直接返回
         */
        if ( rrs.isFinishedRoute() ) {
            return rrs;
        }
        /**
         * 没有from的select语句或其他
         */
        if((ctx.getTables() == null || ctx.getTables().size() == 0)&&(ctx.getTableAliasMap()==null||ctx.getTableAliasMap().isEmpty()))
        {
            return RouterUtil.routeToSingleNode(rrs, schema.getRandomDataNode(), druidParser.getCtx().getSql());
        }
        //如果没有路由计算单元,设置一个
        if(druidParser.getCtx().getRouteCalculateUnits().size() == 0) {
            RouteCalculateUnit routeCalculateUnit = new RouteCalculateUnit();
            druidParser.getCtx().addRouteCalculateUnit(routeCalculateUnit);
        }
        
        SortedSet<RouteResultsetNode> nodeSet = new TreeSet<RouteResultsetNode>();
        boolean isAllGlobalTable = RouterUtil.isAllGlobalTable(ctx, schema);
        //对sql解析出来的每个路由计算单元进行路由计算,计算结果合并存在nodeSet
        //nodeset是一个set会自动去掉重复元素
        for(RouteCalculateUnit unit: druidParser.getCtx().getRouteCalculateUnits()) {
            RouteResultset rrsTmp = RouterUtil.tryRouteForTables(schema, druidParser.getCtx(), unit, rrs, isSelect(statement), cachePool);
            if(rrsTmp != null&&rrsTmp.getNodes()!=null) {
                for(RouteResultsetNode node :rrsTmp.getNodes()) {
                    nodeSet.add(node);
                }
            }
            if(isAllGlobalTable) {//都是全局表时只计算一遍路由
                break;
            }
        }
        RouteResultsetNode[] nodes = new RouteResultsetNode[nodeSet.size()];
        int i = 0;
        for (RouteResultsetNode aNodeSet : nodeSet) {
            nodes[i] = aNodeSet;
             //如果是insert语句,并且只是单表,是注册过的表,并且是slot的sql,修改语句
              if(statement instanceof MySqlInsertStatement &&ctx.getTables().size()==1&&schema.getTables().containsKey(ctx.getTables().get(0))) {
                  RuleConfig rule = schema.getTables().get(ctx.getTables().get(0)).getRule();
                  if(rule!=null&&  rule.getRuleAlgorithm() instanceof SlotFunction){
                     //修改语句
                     aNodeSet.setStatement(ParseUtil.changeInsertAddSlot(aNodeSet.getStatement(),aNodeSet.getSlot()));
                  }
              }
            i++;
        }       
        rrs.setNodes(nodes);
        //分表
        /**
         *  subTables="t_order$1-2,t_order3"
         *目前分表 1.6 开始支持 幵丏 dataNode 在分表条件下只能配置一个,分表条件下不支持join。
         */
        if(rrs.isDistTable()){
            return this.routeDisTable(statement,rrs);
        }
        return rrs;
    }

路由通过tryRouteForTables 计算获取,代码如下

/**
     * 多表路由
     */
    public static RouteResultset tryRouteForTables(SchemaConfig schema, DruidShardingParseInfo ctx,
            RouteCalculateUnit routeUnit, RouteResultset rrs, boolean isSelect, LayerCachePool cachePool)
            throws SQLNonTransientException {
        List<String> tables = ctx.getTables();
        if(schema.isNoSharding()||(tables.size() >= 1&&isNoSharding(schema,tables.get(0)))) {
            return routeToSingleNode(rrs, schema.getDataNode(), ctx.getSql());
        }
        //只有一个表的
        if(tables.size() == 1) {
            return RouterUtil.tryRouteForOneTable(schema, ctx, routeUnit, tables.get(0), rrs, isSelect, cachePool);
        }
        Set<String> retNodesSet = new HashSet<String>();
        //每个表对应的路由映射
        Map<String,Set<String>> tablesRouteMap = new HashMap<String,Set<String>>();

        //分库解析信息不为空
        Map<String, Map<String, Set<ColumnRoutePair>>> tablesAndConditions = routeUnit.getTablesAndConditions();
        if(tablesAndConditions != null && tablesAndConditions.size() > 0) {
            //为分库表找路由
            RouterUtil.findRouteWithcConditionsForTables(schema, rrs, tablesAndConditions, tablesRouteMap, ctx.getSql(), cachePool, isSelect);
            if(rrs.isFinishedRoute()) {
                return rrs;
            }
        }
        //为全局表和单库表找路由
        for(String tableName : tables) {
            TableConfig tableConfig = schema.getTables().get(tableName.toUpperCase());
            if(tableConfig == null) {
                //add 如果表读取不到则先将表名从别名中读取转化后再读取
                String alias = ctx.getTableAliasMap().get(tableName);
                if(!StringUtil.isEmpty(alias)){
                    tableConfig = schema.getTables().get(alias.toUpperCase());
                }
                if(tableConfig == null){
                    String msg = "can't find table define in schema "+ tableName + " schema:" + schema.getName();
                    LOGGER.warn(msg);
                    throw new SQLNonTransientException(msg);
                }
                
            }
            if(tableConfig.isGlobalTable()) {//全局表
                if(tablesRouteMap.get(tableName) == null) {
                    tablesRouteMap.put(tableName, new HashSet<String>());
                }
                tablesRouteMap.get(tableName).addAll(tableConfig.getDataNodes());
            } else if(tablesRouteMap.get(tableName) == null) { //余下的表都是单库表
                tablesRouteMap.put(tableName, new HashSet<String>());
                tablesRouteMap.get(tableName).addAll(tableConfig.getDataNodes());
            }
        }

        boolean isFirstAdd = true;
        for(Map.Entry<String, Set<String>> entry : tablesRouteMap.entrySet()) {
            if(entry.getValue() == null || entry.getValue().size() == 0) {
                throw new SQLNonTransientException("parent key can't find any valid datanode ");
            } else {
                if(isFirstAdd) {
                    retNodesSet.addAll(entry.getValue());
                    isFirstAdd = false;
                } else {
                    retNodesSet.retainAll(entry.getValue());
                    if(retNodesSet.size() == 0) {//两个表的路由无交集
                        String errMsg = "invalid route in sql, multi tables found but datanode has no intersection "
                                + " sql:" + ctx.getSql();
                        LOGGER.warn(errMsg);
                        throw new SQLNonTransientException(errMsg);
                    }
                }
            }
        }

        if(retNodesSet != null && retNodesSet.size() > 0) {
            String tableName = tables.get(0);
            TableConfig tableConfig = schema.getTables().get(tableName.toUpperCase());
            if(tableConfig.isDistTable()){
                routeToDistTableNode(tableName,schema, rrs, ctx.getSql(), tablesAndConditions, cachePool, isSelect);
                return rrs;
            }

            if(retNodesSet.size() > 1 && isAllGlobalTable(ctx, schema)) {
                // mulit routes ,not cache route result
                if (isSelect) {
                    rrs.setCacheAble(false);
                    routeToSingleNode(rrs, retNodesSet.iterator().next(), ctx.getSql());
                }
                else {//delete 删除全局表的记录
                    routeToMultiNode(isSelect, rrs, retNodesSet, ctx.getSql(),true);
                }

            } else {
                routeToMultiNode(isSelect, rrs, retNodesSet, ctx.getSql());
            }
        }
        return rrs;
    }

对sql解析出来的每个路由计算单元进行路由计算

路由计算单元

类:DefaultDruidParser

/**
conditionList  是从sql中提取的查询条件包含信息<表名,字段名,值,操作符>, 如果values为多值 操作符为between,建构RangeValue。
一个sql语句中是用or来分割的,每个List<Condition>为一个or分割的条件list

**/
private List<RouteCalculateUnit> buildRouteCalculateUnits(SchemaStatVisitor visitor, List<List<Condition>> conditionList) {
        List<RouteCalculateUnit> retList = new ArrayList<RouteCalculateUnit>();
        //遍历condition ,找分片字段
        for(int i = 0; i < conditionList.size(); i++) {
            RouteCalculateUnit routeCalculateUnit = new RouteCalculateUnit();
            for(Condition condition : conditionList.get(i)) {
                List<Object> values = condition.getValues();
                if(values.size() == 0) {
                    continue;  
                }
                if(checkConditionValues(values)) {
                    String columnName = StringUtil.removeBackquote(condition.getColumn().getName().toUpperCase());
                    String tableName = StringUtil.removeBackquote(condition.getColumn().getTable().toUpperCase());
                    
                    if(visitor.getAliasMap() != null && visitor.getAliasMap().get(tableName) != null 
                            && !visitor.getAliasMap().get(tableName).equals(tableName)) {
                        tableName = visitor.getAliasMap().get(tableName);
                    }

                    if(visitor.getAliasMap() != null && visitor.getAliasMap().get(StringUtil.removeBackquote(condition.getColumn().getTable().toUpperCase())) == null) {//子查询的别名条件忽略掉,不参数路由计算,否则后面找不到表
                        continue;
                    }
                    
                    String operator = condition.getOperator();
                    
                    //只处理between ,in和=3中操作符
                    if(operator.equals("between")) {
                        RangeValue rv = new RangeValue(values.get(0), values.get(1), RangeValue.EE);
                                routeCalculateUnit.addShardingExpr(tableName.toUpperCase(), columnName, rv);
                    } else if(operator.equals("=") || operator.toLowerCase().equals("in")){ //只处理=号和in操作符,其他忽略
                                routeCalculateUnit.addShardingExpr(tableName.toUpperCase(), columnName, values.toArray());
                    }
                }
            }
            retList.add(routeCalculateUnit);
        }
        return retList;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 截取数字hash解析 当我们需要仅对分片索引字段中的数字部分作为分片依据时,可以使用这个路由规则来将“从第x字符开...
    john_zhong阅读 679评论 0 0
  • 一致性hash 一般来说,基于hash算法的分片中,算法内部是把记录分片到一种叫做“bucket”(hash桶)的...
    john_zhong阅读 1,335评论 0 0
  • 自然月分片 流水账之类的表,常常会有“保存至少24个月的交易数据”、“业务的查询以月为单位”之类的需求或情况。在这...
    john_zhong阅读 1,029评论 2 0
  • 她从是个乖乖女,有良好的家庭背景,爸爸是个有学问的编辑,妈妈温良涵养,外婆也很有淑女气质。本来平静的生活,因为一个...
    LOVE玲媛阅读 609评论 0 0