此文已由作者张镐薪授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

对于表的dataNode对应关系,有个特殊配置即类似dataNode="distributed(dn$1-10)",这个含义是:

/**
     * distribute datanodes in multi hosts,means ,dn1 (host1),dn100
     * (host2),dn300(host3),dn2(host1),dn101(host2),dn301(host3)...etc
     *    将每个host上的datanode按照host重新排列。比如上面的例子host1拥有dn1,dn2,host2拥有dn100,dn101,host3拥有dn300,dn301,
     * 按照host重新排列: 0->dn1 (host1),1->dn100(host2),2->dn300(host3),3->dn2(host1),4->dn101(host2),5->dn301(host3)
     *
     * @param theDataNodes
     */
    private void distributeDataNodes(ArrayList<String> theDataNodes) {
        Map<String, ArrayList<String>> newDataNodeMap = new HashMap<String, ArrayList<String>>(dataHosts.size());        for (String dn : theDataNodes) {
            DataNodeConfig dnConf = dataNodes.get(dn);
            String host = dnConf.getDataHost();
            ArrayList<String> hostDns = newDataNodeMap.get(host);
            hostDns = (hostDns == null) ? new ArrayList<String>() : hostDns;
            hostDns.add(dn);
            newDataNodeMap.put(host, hostDns);
        }         ArrayList<String> result = new ArrayList<String>(theDataNodes.size());        boolean hasData = true;        while (hasData) {
            hasData = false;            for (ArrayList<String> dns : newDataNodeMap.values()) {                if (!dns.isEmpty()) {
                    result.add(dns.remove(0));
                    hasData = true;
                }
            }
        }
        theDataNodes.clear();
        theDataNodes.addAll(result);
    }

读取完所有表之后,记录好DB类型,这对之后的sql语句路由解析有帮助。将所有schema的配置保存在:

private final Map<String, SchemaConfig> schemas;

4.3 server.xml

之后会读取载入server配置。XMLConfigLoader.java:

public XMLConfigLoader(SchemaLoader schemaLoader) {
    XMLServerLoader serverLoader = new XMLServerLoader();    this.system = serverLoader.getSystem();    this.users = serverLoader.getUsers();    this.quarantine = serverLoader.getQuarantine();    this.cluster = serverLoader.getCluster();    this.dataHosts = schemaLoader.getDataHosts();    this.dataNodes = schemaLoader.getDataNodes();    this.schemas = schemaLoader.getSchemas();
    schemaLoader = null;
}

XMLServerLoader.java

public XMLServerLoader() {    this.system = new SystemConfig();    this.users = new HashMap<String, UserConfig>();    this.quarantine = new QuarantineConfig();    this.load();
}private void load() {    //读取server.xml配置
    InputStream dtd = null;
    InputStream xml = null;    try {
        dtd = XMLServerLoader.class.getResourceAsStream("/server.dtd");
        xml = XMLServerLoader.class.getResourceAsStream("/server.xml");
        Element root = ConfigUtil.getDocument(dtd, xml).getDocumentElement();        //加载System标签
        loadSystem(root);        //加载User标签
        loadUsers(root);        //加载集群配置
        this.cluster = new ClusterConfig(root, system.getServerPort());        //加载权限和黑白名单
        loadQuarantine(root);
    } catch (ConfigException e) {        throw e;
    } catch (Exception e) {        throw new ConfigException(e);
    } finally {        if (dtd != null) {            try {
                dtd.close();
            } catch (IOException e) {
            }
        }        if (xml != null) {            try {
                xml.close();
            } catch (IOException e) {
            }
        }
    }
}

首先加载System标签

5. 路由模块

5.1 路由模块组成:

路由模块,我们可以先把他当做个黑盒,看下输入和输出都是神马。输入,很明显,就是个SQL语句,原生的,不加任何修饰的,纯洁的,从客户端发过来刚刚被解码的SQL语句。
输出呢?就是个优化,改写后的SQL语句,以及要发送到的后台分片。
这个RouteResultSet就是输出,长什么样子呢?
下图是主要涉及到的类:

  • RouteResultSet:

    • sqlType:SQL类型(select?insert?…)

    • nodes: 语句和Datanode对应关系。一条语句可以根据不同节点拆成多条不同语句

    • subTables:分表,1.6后功能,单node多表

    • sqlStatement:经过DruidParser解析后的语句

    • limitStart,limitSize:含有limit的SQL的起始点和长度

    • cacheAble:是否可以缓存(MyCat缓存中会保存SQL(key)->RouteResultSet(value))

    • primaryKey:为了实现以后完整的主键缓存而预留

    • sqlMerge:带有合并函数的sql语句处理类

    • callStatement:是否为调用存储过程的语句(call)

    • globalTableFlag:操作表是否包含全局表

    • isFinishedRoute:是否路由完成

    • autocommit:是否为自动提交

    • isLoadData:是否是LoadData命令

    • canRunInReadDb:是否能在读节点上运行

    • runOnSlave:是否在从节点上运行

    • procedure:调用存储过程处理类

  • RouteResultSetNode:

    • serialVersionUID:全局序列化类版本标识

    • name:数据节点名称

    • statement:实际执行的语句

    • srcStatement:源语句

    • sqlType:sql类型

    • canRunInReadDB:是否可以在读节点运行

    • hasBlanceFlag:是否包含balance属性

    • hintMap:注解类型和注解sql语句的map

    • 其他类似

对于路由模块,他需要完成的操作就是MyCat的核心功能之一,将前端发送过来的SQL语句路由到后面合适的分片上。那么,我们至少需要从SQL中解析出来这个SQL对应的是那张表,对应的分片规则是什么?有没有筛选条件,根据筛选条件我们是不是能路由到某几个分片上。是不是插入语句,需不需要生成全局唯一ID?等等等等
MyCat路由模块,大致上包括SQL语句分类,SQL语义解析,SQL语句改写,全局ID生成。

5.2 SQL语句分类

首先,我们先回顾下,SQL语句通过客户端发送给了MyCat,MyCat在前端连接模块完成包解码,在这之后,对SQL语句进行分类处理(其实就是构建自己一套简单的语法分支)。
如何分类?其实就是通过语句第一个词先进行第一步分类:

ServerQueryHandler.java:

public void query(String sql) {

        ServerConnection c = this.source;        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(new StringBuilder().append(c).append(sql).toString());
        }        //
        int rs = ServerParse.parse(sql);        int sqlType = rs & 0xff;        switch (sqlType) {        //explain sql
        case ServerParse.EXPLAIN:
            ExplainHandler.handle(sql, c, rs >>> 8);            break;        //explain2 datanode=? sql=?
        case ServerParse.EXPLAIN2:
            Explain2Handler.handle(sql, c, rs >>> 8);            break;        case ServerParse.SET:
            SetHandler.handle(sql, c, rs >>> 8);            break;        case ServerParse.SHOW:
            ShowHandler.handle(sql, c, rs >>> 8);            break;        case ServerParse.SELECT:            if(QuarantineHandler.handle(sql, c)){
                SelectHandler.handle(sql, c, rs >>> 8);
            }            break;        case ServerParse.START:
            StartHandler.handle(sql, c, rs >>> 8);            break;        case ServerParse.BEGIN:
            BeginHandler.handle(sql, c);            break;        //不支持oracle的savepoint事务回退点
        case ServerParse.SAVEPOINT:
            SavepointHandler.handle(sql, c);            break;        case ServerParse.KILL:
            KillHandler.handle(sql, rs >>> 8, c);            break;        //不支持KILL_Query
        case ServerParse.KILL_QUERY:
            LOGGER.warn(new StringBuilder().append("Unsupported command:").append(sql).toString());
            c.writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,"Unsupported command");            break;        case ServerParse.USE:
            UseHandler.handle(sql, c, rs >>> 8);            break;        case ServerParse.COMMIT:
            c.commit();            break;        case ServerParse.ROLLBACK:
            c.rollback();            break;        case ServerParse.HELP:
            LOGGER.warn(new StringBuilder().append("Unsupported command:").append(sql).toString());
            c.writeErrMessage(ErrorCode.ER_SYNTAX_ERROR, "Unsupported command");            break;        case ServerParse.MYSQL_CMD_COMMENT:
            c.write(c.writeToBuffer(OkPacket.OK, c.allocate()));            break;        case ServerParse.MYSQL_COMMENT:
            c.write(c.writeToBuffer(OkPacket.OK, c.allocate()));            break;            case ServerParse.LOAD_DATA_INFILE_SQL:
                c.loadDataInfileStart(sql);                break;        default:            if(readOnly){
                LOGGER.warn(new StringBuilder().append("User readonly:").append(sql).toString());
                c.writeErrMessage(ErrorCode.ER_USER_READ_ONLY, "User readonly");                break;
            }            if(QuarantineHandler.handle(sql, c)){
                c.execute(sql, rs & 0xff);
            }
        }
    }

每种语句都有自己对应的Handler,我们这里将用Select语句举例。第一个词决定语句是什么类型(CURD)的,第二个词将更细粒度的区分语句,这里是不同的Select。首先我们思考下,不是所有的select语句都需要路由到后面数据库的。比如 select version这样的语句,可以直接回复MyCat的version。还有select LAST_INSERT_ID这样的(MyCat),上次插入的全局ID是在MyCat会缓存的。
所以,MyCat对于select的第二个词也做解析,可以将select语句分为可以直接回复的和必须路由到后面分片得到结果的。SelectHandler.java:

免费体验云安全(易盾)内容安全、验证码等服务

更多网易技术、产品、运营经验分享请点击

相关文章:
【推荐】 为Docker容器设置静态IP
【推荐】 Spring Boot 学习系列(05)—自定义视图解析规则

数据库路由中间件MyCat - 源代码篇(14)的更多相关文章

  1. 数据库路由中间件MyCat - 源代码篇(1)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 进入了源代码篇,我们先从整体入手,之后拿一个简单流程前端连接建立与认证作为例子,理清代码思路和设计模式.然后 ...

  2. 数据库路由中间件MyCat - 源代码篇(13)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 4.配置模块 4.2 schema.xml 接上一篇,接下来载入每个schema的配置(也就是每个MyCat ...

  3. 数据库路由中间件MyCat - 源代码篇(7)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.4 FrontendConnection前端连接 构造方法: public Fronte ...

  4. 数据库路由中间件MyCat - 源代码篇(15)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. public static void handle(String stmt, ServerConnectio ...

  5. 数据库路由中间件MyCat - 源代码篇(17)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 调用processInsert(sc,schema,sqlType,origSQL,tableName,pr ...

  6. 数据库路由中间件MyCat - 源代码篇(4)

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...

  7. 数据库路由中间件MyCat - 源代码篇(2)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...

  8. 数据库路由中间件MyCat - 源代码篇(16)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 5. 路由模块 真正取得RouteResultset的步骤:AbstractRouteStrategy的ro ...

  9. 数据库路由中间件MyCat - 源代码篇(10)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.5 后端连接 3.5.2 后端连接获取与维护管理 还是那之前的流程, st=>st ...

随机推荐

  1. 我的Java开发学习之旅------>求N内所有的素数

    一.素数的概念 质数(prime number)又称素数,有无限个.一个大于1的自然数,除了1和它本身外,不能被其他自然数(质数)整除,换句话说就是该数除了1和它本身以外不再有其他的因数:否则称为合数 ...

  2. python网络爬虫之requests库 二

    前面一篇在介绍request登录CSDN网站的时候,是采用的固定cookie的方式,也就是先通过抓包的方式得到cookie值,然后将cookie值加在发送的数据包中发送到服务器进行认证. 就好比获取如 ...

  3. python get post模拟请求

    1.使用get方式时.url相似例如以下格式: &op=bind   GET报问头例如以下: &n=asa HTTP/1.1    Accept: */*    Accept-Lang ...

  4. 关于wx.redirectTo、wx.navigateTo失效问题

    问题:在app.json页面中若配置了tabBar,并且要跳转的目标页面也在tabBar中时,那么常用的几种页面跳转方式便失效了.即不能跳转到tabBar中定义的页面. 解决办法:若要跳转至tabBa ...

  5. docker 网络模式研究了许久,其实我们需要的是docker run -p 80:80命令

    我们只是希望能够从外部访问到docker而已,并不需要去折腾该死的网络模式,桥接,host等等. -p: 端口映射,格式为:主机(宿主)端口:容器端口 sudo docker run -t -i  - ...

  6. 窥探 Swift 之别具一格的 Struct 和 Class

    说到结构体和类,还是那句话,只要是接触过编程的小伙伴们对这两者并不陌生.但在Swift中的Struct和Class也有着令人眼前一亮的特性.Struct的功能变得更为强大,Class变的更为灵活.St ...

  7. 5.1 《锋利的jQuery》jQuery对表单的操作

    获取焦点和失去焦点改变样式 改变文本框/滚动条高度 复选框应用 下拉框应用 表单验证 tip1: 注意使用<label>的for标签,对应input的id.(for 属性规定 label ...

  8. 基于springboot的RestTemplate、okhttp和HttpClient对比

    1.HttpClient:代码复杂,还得操心资源回收等.代码很复杂,冗余代码多,不建议直接使用. 2.RestTemplate: 是 Spring 提供的用于访问Rest服务的客户端, RestTem ...

  9. CSS3定时提示动画特效

    在线演示 本地下载

  10. Android开发学习之三——第一个Android程序

    下面我们建立第一个Android程序. 打开Eclipse,开始如下步骤: 1.File ==> New ==> Android Application Project 出现如下窗口: 2 ...