Netty框架的 主要线程是IO线程。线程模型的好坏直接决定了系统的吞吐量、并发性和安全性。react
Netty的线程模型遵循了Reactor的基础线程模型。如下咱们先一块儿看下该模型后端
单线程模型中所有的IO操做都在一个NIO线程上操做:数组
包括接受client的请求,读取client的消息和应答。由于使用的是异步非堵塞的IO,所有的IO操做不会堵塞。理论上一个线程就可以处理所有的IO操做。安全
单线程模型适用小容量的应用。网络
因为在高并发应用 可致使下面问题多线程
一个线程同一时候处理成百上千的链路,性能上没法支撑。并发
即便IO线程cpu 100%也没法知足要求。框架
当NIO线层负载太重,处理速度将变慢,会致使大量的client超时,重发,会更加剧NIO的负载。终于致使系统大量超时异步
一旦IO线程跑飞,会致使整个系统通信模块不可用,形成节点故障socket
该模型组织了 一组线程进行IO的操做
特色:
1. 有专门的NIO线程---acceptor线程用于监听server,接受client的TCP请求
2. 网络操做的读写 由一个IO线程池负责 负责消息的读取 接收 编码和发送
3. 一个IO线程可以同一时候处理N条链路。但是一条链路 仅仅相应一个Io线程。防止并发的操做问题
适合绝大多数场景,但是对于并发百万或者server需要对client握手进行安全认证,认证很耗性能的状况,会致使性能瓶颈。
接受client的链接 不在是一个单独的IO线程,而是一个Nio线程池:
Acceptor接受client的请求并处理完毕后,将新建的socketChannel注冊到IO线程池的某个线程上,由
他负责IO的读写 接编码工做。
Acceptor线程池只负责client的登陆 握手 和 安全认证,一旦链路成
功,将链路注冊到后端的线程池的线程上,有他进行兴许的Io操做。
public void bind(int port) throws Exception {
// 配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
// 绑定port,同步等待成功
ChannelFuture f = b.bind(port).sync()。
// 等待服务端监听port关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel arg0) throws Exception {
arg0.pipeline().addLast(new TimeServerHandler());
}
}
nettyserver在启动的时候,建立了两个NIOEventLoopGroup 独立的Reator线程池,一个用于接收client的TCP链接,一个用于处理IO的相关的读写操做。
Netty线程模型就是在reactor模型的基础上创建的,线程模型并不是一成不变的,经过启动參数的配置,可以在三种中切换。
启动过程,bossGroup 会选择一个EventLoop 需要绑定serverSocketChannel 进行接收client链接;处理后,将准备好的socketchnanell顺利注冊到workGroup下。
Netty 屏蔽NIO通讯的底层细节:
首先建立ServerBootstrap,他是Netty服务端的启动辅助类
设置并绑定Reactor线程池。
Netty的Reactor线程池是EventLoopGroup,它实际就是EventLoop线 程的数组。
EventLoop的职责是处理所有注冊到本线程多路复用器Selector上的Channel
设置NIOserverSocketChannel. Netty经过工厂类,利用反射建立NioServerSocketChannel对象
设置TCP參数
链路创建的时候建立并初始化ChannelPipeline.它本质就是一个负责处理网络事件的职责链,负责管理和运行ChannelHandler。
网络事件以事件流的形式在ChannelPipeline中流转,由ChannelPipeline依据ChannelHandler的运行策略调度ChannelHandler的运行
作为Netty的Reactor线程,因为要处理网络IO读写,因此聚合一个多路复用器对象,它经过open获取一个多路复用器。他的操做主要是在run方法的for循环中运行的。
从调度层面看。也不存在在EventLoop线程中 再启动其余类型的线程用于异步运行其余的任务。这样就避免了多线程并发操做和锁竞争,提高了I/O线程的处理和调度性能。
IO操做是线程是的核心,一旦出现问题,致使其上面的多路复用器和多个链路没法正常工做。所以他需要特别的保护。
他在下面两个方面作了保护处理:
异常可能致使线程跑飞。会致使线程下的所有链路不可用,这时採try{}catch(Throwable) 捕获异常,防止跑飞。出现异常后,可以恢复运行。netty的原则是 某个消息的异常不会致使整个链路的不可用,某个链路的不可用。不能致使其它链路的不可用。
Selector.select 没有任务运行时,可能触发JDK的epoll BUG。这就是著名的JDK epoll BUG,JDK1.7早期版本号 号称攻克了。但是据网上反馈,还有此BUG。
server直接表现为 IO线程的 CPU很是高,可能达到100%,可能会致使节点故障!
!!
为何会发生epoll Bug
Netty的修复策略为:
对Selector的select的操做周期进行统计
对每完毕一次空的select操做进行一次计数
在某周期内(如100ms)连续N此空轮询, 说明触发了epoll死循环BUG
检測到死循环后,重建selector的方式让系统恢复正常
netty採用此策略,完美避免了此BUG的发生。
參考资料:netty权威指南2