Netty必知必会(二)—— I/O 模型和线程模型

I/O 传输问题决定着用什么样的通道将数据发送给对方,BIO、NIO 或者 AIO,IO 模型在很大程度上决定了框架的性能。

线程模型问题决定着数据报如何读取?读取之后的编解码在哪个线程进行,编解码后的消息如何派发,Reactor 线程模型的不同,对性能的影响非常大。

  • Netty的两个线程池,为什么两个,有什么区别,具体说来。
  • Netty初始化的时候需要初始化两个线程池,你能简单说一说吗?
  • 线程池默认多少个线程,为什么这么设置?(netty自带的,默认CPU*2)

一、I/O多路复用

9.2 I/O 多路复用:select/poll/epoll | 小林coding (xiaolincoding.com)

I/O 多路复用的特点是通过一种机制使得一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回,所以说它最大的优势是系统开销小,系统不需要创建或维护新的进程/线程

select/poll 方式

poll select 并没有太⼤的本质区别, 都是使⽤「线性结构」存储进程关注的 Socket 集合,因此都 需要遍历⽂件描述符集合来找到可读或可写的 Socket ,时间复杂度为 O(n) ,⽽且也需要在⽤户态与内核 态之间拷⻉⽂件描述符集合 ,这种⽅式随着并发数上来,性能的损耗会呈指数级⻓。

epoll 方式

epoll 在内核⾥使⽤ 红⿊树来跟踪进程所有待检测的⽂件描述字。 epoll 使⽤事件驱动的机制,内核⾥ 维护了⼀个链表来记录就绪事件。

二、Netty采用异步非阻塞通信

Netty 的 IO 线程 NioEventLoop 由于聚合了多路复用器 Selector,可以同时并发处理成百上千个客户端 Channel,由于读写操作都是非阻塞的,这就可以充分提升 IO 线程的运行效率,避免由于频繁 IO 阻塞导致的线程挂起。另外,由于 Netty 采用了异步通信模式,一个 IO 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 IO 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。

三、Reactor模型

Reactor 模型 和 Proactor 模型-CSDN博客

单线程模型:所有I/O操作都由一个线程完成,即多路复用、事件分发和处理都是在一个Reactor 线程上完成的。既要接收客户端的连接请求,向服务端发起连接,又要发送/读取请求或应答/响应消息。一个NIO 线程同时处理成百上千的链路,性能上无法支撑,速度慢,若线程进入死循环,整个程序不可用,对于高负载、大并发的应用场景不合适。

多线程模型:有一个NIO 线程(Acceptor) 只负责监听服务端,接收客户端的TCP 连接请求;NIO 线程池负责网络IO 的操作,即消息的读取、解码、编码和发送;1 个NIO 线程可以同时处理N 条链路,但是1 个链路只对应1 个NIO 线程,这是为了防止发生并发操作问题。但在并发百万客户端连接或需要安全认证时,一个Acceptor 线程可能会存在性能不足问题。

主从多线程模型:Acceptor 线程用于绑定监听端口,接收客户端连接,将SocketChannel 从主线程池的 Reactor 线程的多路复用器上移除,重新注册到Sub 线程池的线程上,用于处理I/O 的读写等操作,从而保证 mainReactor 只负责接入认证、握手等操作;

四、Netty 架构按照 Reactor 模式设计和实现

在 Netty 的官方 demo 中,推荐使用主从多线程模型。Netty 通过 Reactor 模型基于多路复用器接收并处理用户请求,内部实现了两个线程池, boss 线程池和 worker 线程池,其中 boss 线程池的线程负责处理请求的 accept 事件,当接收到 accept 事件的请求时,把对应的 socket 封装到一个 NioSocketChannel 中,并交给 worker 线程池,其中 worker 线程池负责请求的 read 和 write 事件,由对应的Handler 处理。

事实上,Netty 的线程模型并非固定不变,通过在启动辅助类中创建不同的 EventLoopGroup 实例并通过适当的参数配置,就可以支持上述三种 Reactor 线程模型。正是因为 Netty 对 Reactor 线程模型的支持提供了灵活的定制能力,所以可以满足不同业务场景的性能诉求。

//单线程模型
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(1);

//多线程模型
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();  //默认使用2*CPU核数量的线程池

//主从多线程模型
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();

//服务端启动器
ServerBootstrap b = new ServerBootstrap();
//绑定两个工作线程组
b.group(bossGroup, workerGroup)
...

在这段代码中 bossGroup 对应的就是主反应组(MainReactor),workerGroup 对应的是子反应组(subReactor),而NioEventLoopGroup其实就是一个实现了 Java ExecutorService的线程池,其中的线程数可定制,若不设置线程数参数,则该参数值默认为2 * CPU核数量,在ServerBootstrap的初始化过程中,会为其添加一个实现了acceptor机制的Handler。

五、参考

Netty系列之Netty高性能之道_后端_李林锋_InfoQ精选文章

一文读懂网络框架Netty_文化 & 方法_蔡昱星_InfoQ精选文章

  • 16
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值