什么是IO?
IO是Input、Output的简称,即输入输出。简单说就是读取数据,然后进行系统调用
一、Java IO模型
1、BIO(Blocking IO)
BIO即同步阻塞模型,每个客户端连接对应一个处理线程,在BIO中,accept和read方法都是阻塞操作,没有连接请求时,accept方法阻塞等待,如果无数据读取时,read方法阻塞。
编辑
2、NIO(Non Blocking IO)
NIO是同步非阻塞模型,与BIO相比引入了多路复用器Selector的概念,服务端一个线程可以处理多个请求,客户端的连接请求注册在一个Selector上,服务端通过轮询Selector查看是否有IO请求。
编辑
NIO三大核心组件:
Buffer:数据传输的载体,底层基于数组实现,针对java 基本类型提供了对应的缓冲区类。ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer 和 ShortBuffer,没有 BooleanBuffer。
Channel:用于数据传输,面向缓冲区进行操作,支持双向传输,数据可以从Channel读取到Buffer中,也可以从Buffer写入到Channel中。
Selector:多路复用器也叫选择器,当Channel注册近Selector中后,Selector内部通过不断轮询查询Channel是否有已就绪的IO事件,当Channel发生读写事件就会转入就绪状态,selector监听到后通过SelectionKeys可以获取就绪Channel的集合,然后进行后续IO操作。
3、AIO(NIO 2.0)
AIO是异步非阻塞模型,适用于连接数多且连接时间长的应用,在读写事件完成后由回调服务区通知程序启动线程进行处理。与NIO不同,进行读写操作时,只需直接调用read或write方法,当然这两种方法都是异步的,可以理解为read、write方法都是异步,0完成后主动调用回调函数。
epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,获取事件的时候无需遍历整个被监听的描述符集,只需遍历哪些被内核IO事件异步唤醒而加入Ready队列的描述符集。
二、Reactor线程模型
Reactor模式是基于事件驱动开发的,服务端程序处理传入多路请求并将它们同步分派给对应的请求线程,Reactor模式也叫Dispatcher模式,即IO多路复用统一监听事件,收到事件后分发。
Reactor以NIO为底层支持,其核心包括Reactor和Handler
Reactor:Reactor在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对IO事件做出反应
Handler:处理程序执行IO事件要完成的实际事件,Reactor通过调度对应的处理程序响应IO事件,Handler执行非阻塞操作。
根据Reactor的数量和Handler线程数量的不同,Reactor可以分为三种模型:
- 单线程模型(单Reactor单线程)
- 多线程模型(单Reactor多线程)
- 主从多线程模型(多Reactor多线程)
单线程模型
单线程模型比较简单,Reactor内部通过Selector监控连接事件,收到事件后通过dispatch进行分发,如果是连接建立的事件则由Acceptor处理,通过accept接受连接,并创建Handler进行处理,如果是读写事件直接调用连接对应的Handler来处理。单线程模型最大的弊端就是它是阻塞的,无法做到高性能,适用于Redis那类快速读写的场景。
多线程模型
与单线程相比多了一个线程池进行数据处理,单Reactor承担所有事件的监听和响应,Handler只负责响应事件,不进行业务操作(只进行read和write),业务处理交给线程池。
线程池分配一个线程进行业务处理,然后将响应结果交给主进程的Handler,Handler将结果send给client客户端。
主从多线程模型
多个Reactor,每个Reactor都有自己的Selector选择器,线程和dispatch。主线程中的mainReactor通过自己的Selector监控连接建立事件,收到事件后通过Accpetor接收,将新的连接分配给某个子线程。
子线程中的subReactor将mainReator分配的连接加入连接队列中通过自己的Selector进行监听,并创建一个Handler处理后续事件。
三、Netty线程模型
Netty线程模型是Reactor模式的一个实现
编辑
1、线程组
Netty中抽象了两组线程池BossGroup和WorkerGroup,都是NIOEventLoopGroup类型,BossGroup用来接收客户端发来的连接,WorkerGroup负责具体的处理。
NIOEventLoopGroup包含多个NioEventLoop,管理NioEventLoop的生命周期。每个NioEventLoop中包含一个Nio selector、一个队列,一个线程;线程用来轮询Selector中的Channel的读写事件和对投递到队列里面的事件进行处理。
BossNioEventLoop线程执行步骤:
->1、处理accept事件,与client建立连接,生成NioSocketChannel
->2、将NioSocketChannel注册到WorkerNioEventLoop上的Selector
->3、处理任务队列的任务
WorkerNioEventLoop线程执行步骤:
->1、轮询注册到自己的Selector上的所有NioSockerChannel的read和write事件
->2、处理read和write事件,在对应的NioSocketChannel处理业务
->3、runAllTasks处理任务队列TaskQueue的任务,一些耗时的任务处理可以放到TaskQUeue中慢慢处理,不影响数据在pipeline中的流动处理
public class NettyServer {
public static void main(String[] args) {
/* * 创建两个NioEventLoopGroup对象,bossGroup用作监听客户端请求,workerGroup处理每条链接的数据读写 * */
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
/* * 1、ServerBootstrap 作为引导类引导服务器的启动工作 * 2、group配置上面两个NioEventLoopGroup对象的角色 * 3、channel配置服务端的IO模型:NIO模型->NioSocketChannel; * 4、chilHandler用于配置每条连接的数据读写和业务逻辑 * */
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
}
});
//绑定监听端口
serverBootstrap.bind(8000);
}
2、ChannelPipeline
Netty中将Channel的数据管道抽象为ChannelPipeline,消息在ChannelPipeline中流动和传递。ChannelPipeline持有IO事件拦截器(ChannelHandler)的双向链表,由ChannelHandler对IO事件进行拦截和处理,可以方便的新增和删除ChannelHandler来实现不同的业务逻辑定制,实现对修改封闭对扩展开放。
ChannelPipeline是一系列的ChannelHandler实例,入站和出站事件可以被ChannelPipeline拦截,每一个Channel创建都会绑定一个新的ChannelPipeline。
一个事件将由ChannelInboundHandler或ChannelOutboundHandler处理,ChannelHandlerContext实现转发或传递给下一个ChannelHandler。ChannelHandler处理程序可以通知下一个ChannelHandler执行。Read(入站事件)和Write(出站事件)使用相同的pipeline,入站事件从链表头往后传递到最后一个入站Handler,出站事件会从链表尾传递到最前一个出站Handler,两种Handler互不干扰。
ChannelInboundHandler回调方法:
- handlerAdded(): 当业务处理器被加入到流水线后回调
- channelRegistered(): 当channel成功绑定一个NioEventLoop线程后
- channelUnregistered():channel和NioEventLoop线程解除绑定
- channelActive():channel激活时回调
- channelInactive():channel不活跃时回调
- channelRead(): channel从远程读取到数据时回调
- channelReadComplete():read消费完读取的数据后回调
- channelRemoved():netty移除channel上的所有业务处理器,并回调所有业务处理器的handlerRemoved()
3、异步非阻塞
在使用Netty进行网络通信时,我们发起IO请求后会马上得到返回结果,不会阻塞业务调用线程,即使是获取响应结果也不需要业务调用线程使用阻塞的方式等待,而是通过IO线程异步通知的方式。
写操作:Netty通过在ChannelPipeline中判断NioSocketChannel的write的调用线程是不是其对应的NioEventLoop中的线程,如果不是则会把写入请求封装为WriteTask投递到期对应的NIOEventLoop中的队列里面,然后等其对应的NioEventLoop中的线程轮询读写事件的时候,从队列中取出并执行。
读操作:当从NioSocketChannel中读取数据时,NioEventLoop轮询线程发现Selector中有数据就绪,然后事件通知方式通知业务数据已就绪,进行数据读取。