同步阻塞IO: 用户进程发起IO操作后 等待IO操作完成 用户进程才能运行
同步非阻塞IO:用户进程发起IO操作后做其他事情,用户进程时不时询问IO操作是否就绪,从而引入不必要CPU资源
异步阻塞IO:指应用发起一个IO操作后 不等待内核IO操作完成,内核完成IO操作后会通知应用程序
同步和异步的区别:同步必须等待或主动询问IO是否完成 而异步不会
同步I/O中select,poll,epoll都需要在读写事件就绪后自己负责进行读写,这个读写过程是阻塞的 (Selector.select()阻塞可以监听多个句柄 在NIO中实际上是一条线程拥有很多的IO,有任何一个IO有数据 ,selector就被唤醒)
异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间
NIO是Java中的一种同步非阻塞IO,NIO是面向buffer的非阻塞IO
通过selector(Selector用于监听多个Channel的),
Channel(读取或者写入数据,对Channel的读写必须通过buffer对象),
buffer(存放待读写数据的容器) 实现非阻塞io操作,用到了反应器设计模式(react)
nio多路复用的原理
使用NIO的服务端内部有一个多路轮询器selector,用于监听端口,当客户端尝试连接时,selector就会收到通知,但并不会直接创建一个线程来建立连接,而是交给内核去处理,当客户端把数据都传送过来,进入nio的缓存中时,服务端才会创建一个线程,通过一个Channel去接受数据。
只有当数据准备好所有资源,请求IO时,才会开启一个新的线程
Reactor模型中主要有三种角色:
Reactor:内部封装了一个selector,循环调用select方法获得就绪channel。然后将就绪channel dispatch给对应handler执行真的读写逻辑
Acceptor:监听客户端连接,并为客户端的SocketChannel向Reactor注册对应的handler
Handlers:真正执行非阻塞读/写任务逻辑
nio多路复用I/O模型利用select、poll、epoll可以同时监视多个流的I/O事件,在空闲时,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就会从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll只轮询哪些真正发出了事件的流),并且只依次顺序处理就绪的流,这种做法就避免了大量的无用操作
由于JDK的selector在linux等操作系统上时通过epoll实现,它没有连接句柄数的限制,所以selector线程可以同时处理成千上万个客户端连接,且性能不会下降
Netty是完全基于NIO实现的 对TCP、UDP和文件传输协议的支持
Netty的架构类似于多Reactor多线程模型,但是Netty默认不使用Worker线程池执行Handler,而是直接使用IO线程执行读写任务
Netty避免线程切换,Netty在很多地方进行了无锁化设计,通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行
零拷贝
传统的BIO模式(同步阻塞) 数据从磁盘–》内核的read buffer–》用户缓冲区–》内核的socket buffer–》网卡接口的缓冲区
IO流程包含两次用户态和内核态上下文切换,在高并发场景下,这些会很繁杂。
因此,Linux提出了零拷贝的概念:即避免用户态和内核态的切换,直接在内核中进行数据传递。
Linux提供了两个函数mmap和sendfile来实现零拷贝
mmap: 内存映射文件,即将文件的一段直接映射到内存,内核和应用进程共用同一块内存地址
sendfile: 从内核缓冲区直接复制到socket缓冲区, 不需要向应用进程缓冲区拷贝
NIO非阻塞Linux sendfile()实现
操作系统内核中,数据由read buffer->socket buffer
netty层面:指避免数据在用户态中冗余拷贝,提高数据的传输速率
Netty的接收和发送ByteBuffer采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写 //无需多次拷贝
Netty: 调用transferTo,数据从文件由DMA引擎–》内核read buffer-》网卡接口buffer
netty设计模式
责任链模式:将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止
netty中handler的执行顺序
channelInboundHandler按照注册的先后顺序执行
channelOutboundHandler按照注册的先后顺序逆序执行