一、3种 IO 类型
根据消息通信机制来分,IO分为同步与异步
同步:调用者主动等待调用的结果,发出调用后,在没有得到结果之前该调用就不返回;
异步:发出调用后就直接返回了,但是没有结果。被调用者会在调用真正执行完后,通过状态或者回调函数将结果通知调用者。根据程序在等待调用结果时的状态来分,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)之间的关系。为了维护好这个关系,下面这一点是最基本的要求:
前端空闲连接自动关闭时间<=后端空闲连接自动关闭时间