Netty线程模型(Netty的高效原理探秘)

缘起

日前在看netty的工作原理,对netty的线程模型很是不能理解,查阅了诸多资料,终于有了一些眉目。特此记录,已被查阅。

阅读对象

netty是基于java Nio的封装,所以需要读者对java NIO有一定的了解,篇幅所限,本文不会对NIO再做详述,有需要的读者可以查看JAVA BIO,NIO,AIO详解(附代码实现)以及Netty的简介

Netty的线程模型

说netty的线程模型之前,先说传统NIO的使用方式中的关键代码
在这里插入图片描述
可以看到是当前线程获得了客户端的连接之后再从新吧channel注册到selector上,同时把事件注册为读,这样就可以开始读消息了。也就是说获取tcp连接和读取消息都是在同一个线程里面处理的。那么这种方式有什么问题呢?对于一些小容量应用场景,可以使用单线程模型。但是对于高负载、大并发的应用场景却不合适,主要原因如下:

  1. 一个 NIO 线程同时处理成百上千的链路,性能上无法支撑,即便 NIO 线程的 CPU 负荷达到 100%,也无法满足海量消息的编码、解码、读取和发送;
  2. 当 NIO 线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了 NIO 线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈;
  3. 可靠性问题:一旦 NIO 线程意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。

那么你可能回想,既然一个线程不行,那我在读取消息的时候采用线程池不就可以了吗?的确是可行的,事实上,在Netty中也确实是这么做的。

先说netty的Helloword
在这里插入图片描述
可以看到开头new了两个EventLoopGroup,EventLoopGroup可以暂时理解为一个线程组。其中bossGroup负责处理客户端的 TCP 连接请求,如果系统只有一个服务端端口需要监听,则建议 bossGroup 线程组线程数设置为 1。
workerGroup 是真正负责 I/O 读写操作的线程组
先看一张netty的执行流程图
在这里插入图片描述
图中可以看到boosGroup在接收到后会重新注册到workerGroup上,那么bossGroup是如何处理tcp的连接请求同时把他注册到workerGroup上的呢?其实workerGroup中的每一个线程,都有一个多路复用器 Selector,**bossGroup每接收到一个客户端连接,就会从workerGroup选择一个线程然后把channel注册到它的Selector上。**下面看看伪代码实现
bossGroup在接收到客户端后并重新注册到workerGroup上的代码伪实现
在这里插入图片描述
在这里插入图片描述
这样这个workerGroup中的这个线程就会开始读取数据,下面看看worker线程读取数据的伪代码实现(其实就是普通的NIO中读取数据的方式)
在这里插入图片描述
总结:bossGroup负责处理客户端的 TCP 连接请求,bossGroup每接收到一个客户端连接,就会从workerGroup选择一个线程然后把channel注册到它的Selector上,这样的话请求接收和请求处理就通过不同的线程分开了,这也是netty高效的原因之一。当然Netty高效的原因绝不仅仅是由于优秀的线程模型的设计,与Netty的编码协议,virtual buffer,以及Zero-Copy(零拷贝)也息息相关 。

附录:
手写Netty入门代码:netty的简单版实现,代码基本列举了netty中的关键代码,去掉了一些逻辑判断。看看基本上就懂了。

参考:
https://stackoverflow.com/questions/57532940/why-should-nettys-boss-re-register-to-the-worker-when-getting-the-client-connec
https://blog.csdn.net/hao707822882/article/details/39544553

发布了114 篇原创文章 · 获赞 146 · 访问量 35万+

猜你喜欢

转载自blog.csdn.net/qq32933432/article/details/99696840