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

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

3. 连接模块

3.5 后端连接

对于后端连接,我们只关心MySQL的。
从后端连接工厂开始MySQLConnectionFactory.java:

public MySQLConnection make(MySQLDataSource pool, ResponseHandler handler,
            String schema) throws IOException {        //DBHost配置
        DBHostConfig dsc = pool.getConfig();        //根据是否为NIO返回SocketChannel或者AIO的AsynchronousSocketChannel
        NetworkChannel channel = openSocketChannel(MycatServer.getInstance()
                .isAIO());        //新建MySQLConnection
        MySQLConnection c = new MySQLConnection(channel, pool.isReadNode());        //根据配置初始化MySQLConnection
        MycatServer.getInstance().getConfig().setSocketParams(c, false);
        c.setHost(dsc.getIp());
        c.setPort(dsc.getPort());
        c.setUser(dsc.getUser());
        c.setPassword(dsc.getPassword());
        c.setSchema(schema);        //目前实际连接还未建立,handler为MySQL连接认证MySQLConnectionAuthenticator
        c.setHandler(new MySQLConnectionAuthenticator(c, handler));
        c.setPool(pool);
        c.setIdleTimeout(pool.getConfig().getIdleTimeout());        //AIO和NIO连接方式建立实际的MySQL连接
        if (channel instanceof AsynchronousSocketChannel) {
            ((AsynchronousSocketChannel) channel).connect(                    new InetSocketAddress(dsc.getIp(), dsc.getPort()), c,
                    (CompletionHandler) MycatServer.getInstance()
                            .getConnector());
        } else {            //通过NIOConnector建立连接
            ((NIOConnector) MycatServer.getInstance().getConnector())
                    .postConnect(c);         }        return c;
    }

通过NIOConnector建立实际连接的过程与前端连接的建立相似,也是先放在队列中,之后由NIOConnector去建立连接。

public void postConnect(AbstractConnection c) {
    connectQueue.offer(c);
    selector.wakeup();
}public void run() {    final Selector tSelector = this.selector;    for (;;) {
        ++connectCount;        try {            //查看有无连接就绪
            tSelector.select(1000L);
            connect(tSelector);
            Set<SelectionKey> keys = tSelector.selectedKeys();            try {                for (SelectionKey key : keys) {
                    Object att = key.attachment();                    if (att != null && key.isValid() && key.isConnectable()) {
                        finishConnect(key, att);
                    } else {
                        key.cancel();
                    }
                }
            } finally {
                keys.clear();
            }
        } catch (Exception e) {
            LOGGER.warn(name, e);
        }
    }
}private void connect(Selector selector) {
    AbstractConnection c = null;    while ((c = connectQueue.poll()) != null) {        try {
            SocketChannel channel = (SocketChannel) c.getChannel();            //注册OP_CONNECT监听与后端连接是否真正建立
            channel.register(selector, SelectionKey.OP_CONNECT, c);               //主动连接
            channel.connect(new InetSocketAddress(c.host, c.port));
        } catch (Exception e) {
            c.close(e.toString());
        }
    }
}private void finishConnect(SelectionKey key, Object att) {
    BackendAIOConnection c = (BackendAIOConnection) att;    try {        if (finishConnect(c, (SocketChannel) c.channel)) { //做原生NIO连接是否完成的判断和操作
            clearSelectionKey(key);
            c.setId(ID_GENERATOR.getId());               //绑定特定的NIOProcessor以作idle清理
            NIOProcessor processor = MycatServer.getInstance()
                    .nextProcessor();
            c.setProcessor(processor);               //与特定NIOReactor绑定监听读写
            NIOReactor reactor = reactorPool.getNextReactor();
            reactor.postRegister(c);
        }
    } catch (Exception e) {        //如有异常,将key清空
           clearSelectionKey(key);
           c.close(e.toString());
        c.onConnectFailed(e);
    }
}private boolean finishConnect(AbstractConnection c, SocketChannel channel)
        throws IOException {    if (channel.isConnectionPending()) {
        channel.finishConnect();
        c.setLocalPort(channel.socket().getLocalPort());        return true;
    } else {        return false;
    }
}private void clearSelectionKey(SelectionKey key) {    if (key.isValid()) {
        key.attach(null);
        key.cancel();
    }
}

绑定到具体的NIOReactor之后,监听读事件。和之前讲的前端连接建立过程类似。这次是后端MySQL主动发握手包。这时,读事件就绪,NIOReactor中的RW线程会调用对应AbstractConnection(这里是MySQLConnection)的handler的处理方法处理。这里MySQLConnection中的handler参考工厂方法发现是MySQLConnectionAuthenticator。查看handle方法:

/**
 * MySQL 4.1版本之前是MySQL323加密,MySQL 4.1和之后的版本都是MySQLSHA1加密,在MySQL5.5的版本之后可以客户端插件式加密(这个MyCat实现)
 * @see @http://dev.mysql.com/doc/internals/en/determining-authentication-method.html
 */@Overridepublic void handle(byte[] data) {    try {        switch (data[4]) {        //如果是OkPacket,检查是否认证成功
        case OkPacket.FIELD_COUNT:
            HandshakePacket packet = source.getHandshake();            if (packet == null) {                //如果为null,证明链接第一次建立,处理
                processHandShakePacket(data);                // 发送认证数据包
                source.authenticate();                break;
            }            // 如果packet不为null,处理认证结果
            //首先将连接设为已验证并将handler改为MySQLConnectionHandler
            source.setHandler(new MySQLConnectionHandler(source));
            source.setAuthenticated(true);            //判断是否用了压缩协议
            boolean clientCompress = Capabilities.CLIENT_COMPRESS==(Capabilities.CLIENT_COMPRESS & packet.serverCapabilities);            boolean usingCompress= MycatServer.getInstance().getConfig().getSystem().getUseCompression()==1 ;            if(clientCompress&&usingCompress)
            {
                source.setSupportCompress(true);
            }            //设置ResponseHandler
            if (listener != null) {
                listener.connectionAcquired(source);
            }            break;        //如果为ErrorPacket,则认证失败
        case ErrorPacket.FIELD_COUNT:
            ErrorPacket err = new ErrorPacket();
            err.read(data);
            String errMsg = new String(err.message);
            LOGGER.warn("can't connect to mysql server ,errmsg:"+errMsg+" "+source);            //source.close(errMsg);
            throw new ConnectionException(err.errno, errMsg);        //如果是EOFPacket,则为MySQL 4.1版本,是MySQL323加密
        case EOFPacket.FIELD_COUNT:
            auth323(data[3]);            break;        default:
            packet = source.getHandshake();            if (packet == null) {
                processHandShakePacket(data);                // 发送认证数据包
                source.authenticate();                break;
            } else {                throw new RuntimeException("Unknown Packet!");
            }         }     } catch (RuntimeException e) {        if (listener != null) {
            listener.connectionError(e, source);            return;
        }        throw e;
    }
}

在连接建立并认证后,MySQLConnectionHandler来处理这个连接的请求和相应。
MySQL服务端响应客户端查询请求的流程如下:可以分为三个阶段:
(第一阶段)客户端发送查询请求包COM_QUERY (command query packet),如果有结果集返回,且结果集不为空,则返回FieldCount(列数量)包;如果结果集为空,则返回OKPacket;如果命令有错,则返回ERRPacket;如果是Load file data命令,则返回LOCAL_INFILE_Request。
(第二阶段)如果有结果集返回,则先返回列集合,所有列返回完了之后,会返回EOFPacket;如果过程中出现错误,则返回错误包。
(第三阶段)之后返回行记录,返回全部行记录之后,返回EOFPacket。如果有错误,回错误包。
MyCat实现源代码如下:

protected void handleData(byte[] data) {    switch (resultStatus) {        //第一阶段
        case RESULT_STATUS_INIT:
            switch (data[4]) {                //返回OKPacket
                case OkPacket.FIELD_COUNT:
                    handleOkPacket(data);                    break;                //返回错误包
                case ErrorPacket.FIELD_COUNT:
                    handleErrorPacket(data);                    break;                //返回Load Data进一步操作
                case RequestFilePacket.FIELD_COUNT:
                    handleRequestPacket(data);                    break;                //返回结果集列数量                default:
                    //记录列数量并进入第二阶段
                    resultStatus = RESULT_STATUS_HEADER;
                    header = data;
                    fields = new ArrayList<byte[]>((int) ByteUtil.readLength(data,                            4));
            }            break;        //第二阶段
        case RESULT_STATUS_HEADER:
            switch (data[4]) {                //返回错误包
                case ErrorPacket.FIELD_COUNT:
                    resultStatus = RESULT_STATUS_INIT;
                    handleErrorPacket(data);                    break;                //返回EOF,证明列集合返回完毕,进入第三阶段
                case EOFPacket.FIELD_COUNT:
                    resultStatus = RESULT_STATUS_FIELD_EOF;
                    handleFieldEofPacket(data);                    break;                //返回的是列集合,记录                default:
                    fields.add(data);
            }            break;        //第三阶段
        case RESULT_STATUS_FIELD_EOF:
            switch (data[4]) {                //返回错误包
                case ErrorPacket.FIELD_COUNT:
                    resultStatus = RESULT_STATUS_INIT;
                    handleErrorPacket(data);                    break;                //返回EOF,证明结果集返回完毕,回到第一阶段等待下一个请求的响应
                case EOFPacket.FIELD_COUNT:
                    resultStatus = RESULT_STATUS_INIT;
                    handleRowEofPacket(data);                    break;                //返回结果集包                default:
                    handleRowPacket(data);
            }            break;        default:
            throw new RuntimeException("unknown status!");
    }
}

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

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

相关文章:
【推荐】 IOS渠道追踪方式

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

  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 - 源代码篇(14)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 对于表的dataNode对应关系,有个特殊配置即类似dataNode="distributed(d ...

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

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

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

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

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

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

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

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

随机推荐

  1. Netty Bootstrap(图解)|秒懂

    目录 Netty Bootstrap(图解) 源码工程 写在前面 图解几个重要概念 父子 channel EventLoop 线程与线程组 通道与Reactor线程组 Channel 通道的类型 启动 ...

  2. 【题解】Fence(单调队列)

    [题解]Fence(单调队列) POJ - 1821 题目大意 有\(k\)个粉刷匠,每个粉刷匠一定要粉刷某个位置\(S_i\),一个粉刷匠可以粉刷至多\(l_i\)个位置(必须连续\(l_i\)互不 ...

  3. pandas,apply并行计算的一个demo

    #!/usr/bin/env python # -*- coding: utf-8 -*- # @Date : 2018-10-11 17:55:26 # @Author : Sheldon (thi ...

  4. Java截取最后一个 _ 后面的所有字符

    String file = http://localhost:8888/upload/20190310/115111_58_592_HDFS读取文件的流程.png //截取文件名 String ori ...

  5. 游戏引擎基于Handle的资源管理

    基于Handle的资源管理方案,第一时间想到的应该是Windows了,但是真正想让我实施这个方案的,是<游戏编程精粹1>里面的游戏资源管理篇章的给出的方案.在<游戏编程精粹1> ...

  6. BZOJ 1641 [Usaco2007 Nov]Cow Hurdles 奶牛跨栏:新版floyd【路径上最大边最小】

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1641 题意: 给你一个有向图,n个点(n <= 300),m条边,边权为h[i]. ...

  7. BZOJ 1607 [Usaco2008 Dec]Patting Heads 轻拍牛头:统计 + 筛法【调和级数】

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1607 题意: 给你n个数,问你除a[i]之外,有多少个数是a[i]的约数. 题解: ans ...

  8. 常用JS组件整理

    1.漂亮的弹出层----artDialog http://aui.github.io/artDialog/ 2.弹出层 ------layer http://sentsin.com/jquery/la ...

  9. JavaUtil_03_图片处理工具类

    一.源码 功能:缩放图像.切割图像.图像类型转换.彩色转黑白.文字水印.图片水印等 package com.ray.dingtalk.util; import java.awt.AlphaCompos ...

  10. 基于node.js及express实现中间件,实现post、get

    首先,当然是有必要的环境,安装node,这个我就不多说了. 依赖模块: "express": "^4.13.4", "request": & ...