2.Mycat原理解析-网络通信框架

一、3种 IO 类型

  1. 根据消息通信机制来分,IO分为同步与异步
    同步:调用者主动等待调用的结果,发出调用后,在没有得到结果之前该调用就不返回;
    异步:发出调用后就直接返回了,但是没有结果。被调用者会在调用真正执行完后,通过状态或者回调函数将结果通知调用者。

  2. 根据程序在等待调用结果时的状态来分,IO分为阻塞于非阻塞。
    阻塞:调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回。
    非阻塞:在不能立刻得到结果之前,该调用不会阻塞当前线程。

同步IO有阻塞和非阻塞之分,但是异步IO一定是非阻塞的,所以IO类型有下面3种:

  • 同步阻塞(如:java.net.Socket)
  • 同步非阻塞(如:Java NIO)
  • 异步非阻塞(如:Java AIO)

二、MyCAT的 NIO 实现

  MyCAT起源于Cobar,Cobar前端读写用NIO,后端读写用 BIO(也就是java.net.Socket),所以Cobar 后端每次进行读写都会造成线程阻塞,后端能支持的连接总数就成为瓶颈所在。
  MyCAT 在基于Cobar改版时,直接采用了Java 7的 AIO,前后端都实现了异步非阻塞。但是异步IO需要操作系统支持,由于Linux 并没有真正实现AIO,实际测试下来,AIO 并不比 NIO 快,反而性能上比 NIO 还要慢,所以MyCAT在2014年下半年,做了一次网络通信框架的大调整,改为同时支持 AIO 和 NIO,通过启动参数让用户来选择哪种方式。虽然现在 AIO 比 NIO 慢,但是 MyCAT 仍然保留了 AIO 实现,就是为了等 Linux 真正实现 AIO 后,可以直接支持。

  由于AIO现在未被真正用到,所以下面主要讲到的是MyCAT的NIO实现。虽然NIO的相关知识并不属于本篇的内容,但是为了能更好的理解MyCAT的通信框架,在下面的解析过程中会简单的添加一些NIO 的知识。

1、经典NIO Reactor模式

NIO有四种事件,分别用下面四个常量来表示,每个Channel被注册到Selector时,至少选择其中一种事件。OP_ACCEPT事件一般是服务端接收客户端的请求时使用,通常情况下ServerSocketChannel会注册此事件;OP_CONNECT事件一般是客户端发起连接请求后,等待服务端创建连接,判断连接是否完成时使用; OP_READ是读事件,OP_WRITE是写事件。
  SelectionKey.OP_CONNECT
  SelectionKey.OP_ACCEPT
  SelectionKey.OP_READ
  SelectionKey.OP_WRITE
其中,OP_WRITE事件相对有一点特殊,一般来说,你不应该注册写事件。写操作的就绪条件为底层缓冲区有空闲空间,而写缓冲区绝大部分时间都是有空闲空间的,所以当你注册写事件后,写操作一直是就绪的,选择处理线程全占用整个CPU资源。所以,只有当你确实有数据要写时再注册写操作,并在写完以后马上取消注册。

  NIO大多会采用Reactor模式,下面是经典的Reactor架构,一个Reactor负责处理所有事情,包括接受客户端连接、读取客户端的数据、往客户端写数据。
这里写图片描述

Reactor的实现大多为一个线程,里面一个无线循环,下面是一个简单的示例。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;

public class NIOReactor extends Thread{

    private ServerSocketChannel serverSocketChannel;

    private int port = 9036;

    private Selector selector;

    public NIOReactor() throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 1024 * 16 * 2);
        serverSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);

        SocketAddress socketAddress = new InetSocketAddress(port);
        serverSocketChannel.bind(socketAddress, 100);

        selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    @Override
    public void run() {
        while (true){
            try {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey selectionKey : selectionKeys) {
                    if(!selectionKey.isValid()){
                        System.out.println("selection key is invalid");
                    }else if(selectionKey.isAcceptable()){
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        /**为新的SocketChannel注册读事件*/
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    } else if(selectionKey.isReadable()){
                        /**读取数据*/
                        SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        socketChannel.read(byteBuffer);

                      /**注册写事件,准备待写的数据*/
                selectionKey.interestOps(selectionKey.interestOps() | SelectionKey.OP_WRITE);
                        selectionKey.attach(new Object());
                    } else if (selectionKey.isWritable()){
                        /**写数据*/
                        SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                        Object object = selectionKey.attachment();
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                        byteBuffer.put(ByteBuffer.wrap(object.toString().getBytes()));
                        socketChannel.write(byteBuffer);
/**取消写事件*/                        selectionKey.interestOps(SelectionKey.OP_READ);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2、多Reactor模式
  在高性能 IO 框架中,大都是采用多 Reactor 模式。Main Reactor主要负责处理OP_ACCEPT事件,Sub Reactor负责处理OP_READ和OP_WRITE事件。
这里写图片描述

3、MyCAT Reactor模式
  MyCAT也采用了多Reactor模式,不同的是,MyCAT除了作为一个服务端之外,还需要作为一个客户端去连接MySQL,所以,MyCAT中有一个Main Reactor负责处理OP_CONNECT事件。
这里写图片描述

三、MyCAY一次完整的请求过程

这里写图片描述
  由于MyCAY使用了两层NIO架构,所以需要通过NIOHandler来维护前端连接(FrontendConnection)和后端连接(MySQLConnection)之间的关系。为了维护好这个关系,下面这一点是最基本的要求:
前端空闲连接自动关闭时间<=后端空闲连接自动关闭时间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值