netty的线程池-----揭示了使用两个线程池的原因

本文解析了Netty 5的线程模型,包括NioEventLoop和NioEventLoopGroup的概念,以及如何通过多个Selector处理大量连接。阐述了一个Channel绑定一个NioEventLoop的重要性,确保ChannelHandler的执行顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程模型是Netty的核心设计,设计地很巧妙,之前项目中有一块处理并发的设计和Netty的Eventloop单线程设计类似,效果得到了实证。


Netty5的类层次结构和之前的版本变化很大,网上也有很多文章写Netty的线程模型,Reactor模式,比如这篇http://blog.youkuaiyun.com/xiaolang85/article/details/37873059, 应该是引自《Netty权威指南》,写得比较全面,但是有几个关键的概念没讲清楚。


这篇文章只讲Netty5线程模型最重要的几个关键点

第一个概念是如何理解NioEventLoop和NioEventLoopGroup:NioEventLoop实际上就是工作线程,可以直接理解为一个线程。NioEventLoopGroup是一个线程池,线程池中的线程就是NioEventLoop。Netty设计这几个类的时候,层次结构挺复杂,反而让人迷惑。


还有一个让人迷惑的地方是,创建ServerBootstrap时,要传递两个NioEventLoopGroup线程池,一个叫bossGroup,一个叫workGroup。《Netty权威指南》里只说了bossGroup是用来处理TCP连接请求的,workGroup是来处理IO事件的。

这么说是没错,但是没说清楚bossGroup具体如何处理TCP请求的。实际上bossGroup中有多个NioEventLoop线程,每个NioEventLoop绑定一个端口,也就是说,如果程序只需要监听1个端口的话,bossGroup里面只需要有一个NioEventLoop线程就行了

在上一篇文章介绍服务器端绑定的过程中,我们看到最后是NioServerSocketChannel封装的Java的ServerSocketChannel执行了绑定,并且执行accept()方法来创建客户端SocketChannel的连接。一个端口只需要一个NioServerSocketChannel即可


[java]  view plain copy
  1.                 EventLoopGroup bossGroup = new NioEventLoopGroup();  
  2.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
  3.         try {  
  4.             ServerBootstrap b = new ServerBootstrap();  
  5.             b.group(bossGroup, workerGroup)  
  6.                     .channel(NioServerSocketChannel.class)  
  7.                     .option(ChannelOption.SO_BACKLOG, 1024)  
  8.                     .childHandler(new ChildChannelHandler());  
  9.               
  10.             ChannelFuture f = b.bind(port).sync();  
  11.             f.channel().closeFuture().sync();  
  12.         } finally {  
  13.             bossGroup.shutdownGracefully();  
  14.             workerGroup.shutdownGracefully();  
  15.         }  
  16.   
  17.   
  18.     protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {  
  19.         if (nThreads <= 0) {  
  20.             throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));  
  21.         }  
  22.   
  23.         if (executor == null) {  
  24.             executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());  
  25.         }  
  26.   
  27.         children = new EventExecutor[nThreads];  
  28.         for (int i = 0; i < nThreads; i ++) {  
  29.             boolean success = false;  
  30.             try {  
  31.                 children[i] = newChild(executor, args);  
  32.                 success = true;  
  33.             } catch (Exception e) {  
  34.                 // TODO: Think about if this is a good exception type  
  35.                 throw new IllegalStateException("failed to create a child event loop", e);  
  36.             } finally {  


第二个概念是每个NioEventLoop都绑定了一个Selector,所以在Netty5的线程模型中,是由多个Selecotr在监听IO就绪事件。而Channel注册到Selector。

举个例子,比如有100万个连接连到服务器端。平时的写法可能是1个Selector线程监听所有的IO就绪事件,1个Selector面对100万个连接(Channel)。

而如果使用了1000个NioEventLoop的线程池来说,1000个Selector面对100万个连接,每个Selector只需要关注1000个连接(Channel)

[java]  view plain copy
  1. public final class NioEventLoop extends SingleThreadEventLoop {  
  2.   
  3.     
  4.     /** 
  5.      * The NIO {@link Selector}. 
  6.      */  
  7.     Selector selector;  
  8.     private SelectedSelectionKeySet selectedKeys;  
  9.   
  10.     private final SelectorProvider provider;  


第三个概念是一个Channel绑定一个NioEventLoop相当于一个连接绑定一个线程,这个连接所有的ChannelHandler都是在一个线程中执行的,避免的多线程干扰。更重要的是ChannelPipline链表必须严格按照顺序执行的。单线程的设计能够保证ChannelHandler的顺序执行

[java]  view plain copy
  1. public interface Channel extends AttributeMap, Comparable<Channel> {  
  2.   
  3.     /** 
  4.      * Return the {@link EventLoop} this {@link Channel} was registered too. 
  5.      */  
  6.     EventLoop eventLoop();  

第四个概念是一个NioEventLoop的selector可以被多个Channel注册,也就是说多个Channel共享一个EventLoop。EventLoop的Selecctor对这些Channel进行检查。

这段代码展示了线程池如何给Channel分配EventLoop,是根据Channel个数取模

[java]  view plain copy
  1.  public EventExecutor next() {  
  2.         return children[Math.abs(childIndex.getAndIncrement() % children.length)];  
  3.     }  
  4.   
  5. private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {  
  6.         for (int i = 0;; i ++) {  
  7.             // 逐个处理注册的Channel  
  8.             final SelectionKey k = selectedKeys[i];  
  9.             if (k == null) {  
  10.                 break;  
  11.             }  
  12.   
  13.             final Object a = k.attachment();  
  14.   
  15.             if (a instanceof AbstractNioChannel) {  
  16.                 processSelectedKey(k, (AbstractNioChannel) a);  
  17.             } else {  
  18.                 @SuppressWarnings("unchecked")  
  19.                 NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;  
  20.                 processSelectedKey(k, task);  
  21.             }  
  22.   
  23.             if (needsToSelectAgain) {  
  24.                 selectAgain();  
  25.                 // Need to flip the optimized selectedKeys to get the right reference to the array  
  26.                 // and reset the index to -1 which will then set to 0 on the for loop  
  27.                 // to start over again.  
  28.                 //  
  29.                 // See https://github.com/netty/netty/issues/1523  
  30.                 selectedKeys = this.selectedKeys.flip();  
  31.                 i = -1;  
  32.             }  
  33.         }  
  34.     }  


理解了这4个概念之后就对Netty5的线程模型有了清楚的认识:

在监听一个端口的情况下,一个NioEventLoop通过一个NioServerSocketChannel监听端口,处理TCP连接。后端多个工作线程NioEventLoop处理IO事件。每个Channel绑定一个NioEventLoop线程,1个NioEventLoop线程关联一个selector来为多个注册到它的Channel监听IO就绪事件。NioEventLoop是单线程执行,保证Channel的pipline在单线程中执行,保证了ChannelHandler的执行顺序。

下面这张图来之http://blog.youkuaiyun.com/xiaolang85/article/details/37873059, 基本能说清楚。


Netty5.0 架构剖析和源码解读 作者:李林锋 版权所有 email neu_lilinfeng@ © Netty5.0 架构剖析和源码解读1 1. 概述2 1.1. JAVA 的IO演进2 1.1.1. 传统BIO通信的弊端2 1.1.2. Linux 的网络IO模型简介4 1.1.3. IO复用技术介绍7 1.1.4. JAVA的异步IO8 1.1.5. 业界主流的NIO框架介绍10 2.NIO入门10 2.1. NIO服务端10 2.2. NIO客户端13 3.Netty源码分析16 3.1. 服务端创建16 3.1.1. 服务端启动辅助类ServerBootstrap16 3.1.2. NioServerSocketChannel 的注册21 3.1.3. 新的客户端接入25 3.2. 客户端创建28 3.2.1. 客户端连接辅助类Bootstrap28 3.2.2. 服务端返回ACK应答,客户端连接成功32 3.3. 读操作33 3.3.1. 异步读取消息33 3.4. 写操作39 3.4.1. 异步消息发送39 3.4.2. Flush操作42 4.Netty架构50 4.1. 逻辑架构50 5. 附录51 5.1. 作者简介51 5.2. 使用声明51 1. 概述 1.1.JAVA 的IO演进 1.1.1. 传统BIO通信的弊端 在JDK 1.4推出JAVANIO1.0之前,基于JAVA 的所有Socket通信都采用 BIO 了同步阻塞模式( ),这种一请求一应答的通信模型简化了上层的应用开发, 但是在可靠性和性能方面存在巨大的弊端。所以,在很长一段时间,大型的应 C C++ 用服务器都采用 或者 开发。当并发访问量增大、响应时间延迟变大后, 采用JAVABIO作为服务端的软件只有通过硬件不断的扩容来满足访问量的激 增,它大大增加了企业的成本,随着集群的膨胀,系统的可维护性也面临巨大 的挑战,解决这个问题已经刻不容缓。 首先,我们通过下面这幅图来看下采用BIO 的服务端通信模型:采用BIO 通信模型的 1connect NewThread1 WebBrowse 2connect 2handle(Req) WebBrowse 3connect Acceptor NewThread2 WebBrowse WebBrowse 4connect NewThread3 3sendResponsetopeer NewThread4 图1.1.1-1 BIO通信模型图 服务端,通常由一个独立的Accepto 线程负责监听客户端的连接,接收到客户 端连接之后为客户端连接创建一个新的线程处理请求消息
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值