1.传统的io模型
采用了阻塞io模型 每个请求都会对应一个线程去处理,而在每个线程中当没有数据时,会在read处阻塞,等待数据,造成线程浪费,当请求数增多时,需要创建的线程局增多,造成系统资源浪费。
2.Reactor模型
我现在所学的有nginx,redis都是应用了Reactor模型
采用阻塞对象ServiceHandler,当有线程发出请求时,SH会让请求进入队列,分发给处理线程,在这里SH单独处在一个线程中,在没有请求时进行监听事件,有请求,操作系统通知SH进行处理分发。 和redis多路IO复用很像。
3.单Reactor单线程模型
多个Client进行请求,在阻塞Reactor处进行监听请求,当有连接请求时,交给Acceptor去进行连接,连接后将连接交给事件处理器Handler去进行业务的处理。如果不是连接请求,那么直接分发给Handler进行事件处理。read进行事件读取,send返回处理完后的结果r。但是缺点是事件处理和Reactor监听在一个线程中,处理事件时则无法进行事件监听,性能差。
4单个Reactor多线程
还是单个Reactor进行事件监听,当有连接事件时,会把他交给Acceptor进行连接,然后将业务事件分发给Handler进行转发,Handler读取事件信息,然后交给worker线程池里的一个线程去处理事件,结果返回给Handler,handler把结果send给client。Handler只负责对请求读取和交给工作线程进行处理。虽然提高了效率,但是缺点是进行连接的监听的处理还是在一个单一线程中,性能还是有点低。
5.主从Reactor多线程
主线程只进行连接的监听,如果是连接请求,则Acceptor记性连接,然后将连接分发子线程,子线程将连接加入监听队列中,当有事件发生时,交给事件处理器hanlder,进行read读取,交给worker线程池中的线程去处理,将结果返回给handler,然后handler发给client。
6.Netty模型
Netty模型有两个线程池BossGroup,负责接收客户端的连接,WorkerGroup负责网络的读写。都是NioEventLoopGroup类型。
NioEventLoopGroup相当于一个事件循环组,里面有多个事件循环,当更有连接时,事件循环器进行接收,将连接包装成NioSocketChannel,socket绑定,注册到某个WorkerGroup中的事件循环组中,然后执行任务队列里的非IO任务。
每个WorkerGroup中的NioEventLoop从传来的channel中监听,对read,write事件,如果有事件发生,交给Pipeline中的对应的channel进行处理,channel中有相应的处理器处理,然后在处理任务队列非IO任务。
netty采用串行化设计理念,从消息的读取->解码->处理->编码->发送,始终由IO线程NioEventLoop负责。整个流程不会进行线程上下文切换,数据无并发修改风险。
一个NioEventLoop聚合一个多路复用器selector,因此可以处理多个客户端连接。
netty只负责提供和管理“IO线程”,其他的业务线程模型由用户自己集成。
时间可控的简单业务建议直接在“IO线程”上处理,复杂和时间不可控的业务建议投递到后端业务线程池中处理。
解决JDK的NIO epoll bug
该bug会导致Selector一直空轮询,最终导致cpu 100%。
在每次selector.select(timeoutMillis)后,如果没有监听到就绪IO事件,会记录此次select的耗时。如果耗时不足timeoutMillis,说明select操作没有阻塞那么长时间,可能触发了空轮询,进行一次计数。
计数累积超过阈值(默认512)后,开始进行Selector重建:
a)拿到有效的selectionKey集合
b)取消该selectionKey在旧的selector上的事件注册
c)将该selectionKey对应的Channel注册到新的selector上,生成新的selectionKey
d)重新绑定Channel和新的selectionKey的关系
参考了:https://www.jianshu.com/p/38b56531565d