只看了服务端的线程结构,客户端还没看,不知道是不是一样,所以这篇就叫服务端线程模型吧,以后看了客户端再修改。术语和第二篇一样,不重复写了。
1. Netty与Reactor的关系
Netty是Reactor模型的实现,有关Reactor有一张经典的图:
通过前面的分析,大概能知道:
① main reactor即NioServerBoss,由它的selector负责监听端口,注册连接事件,并处理accept,充当上面的acceptor的作用,main reactor用到的线程池这个图中没画出来,这个boss线程池发挥的作用不是很重要,主要是IOT。
② sub reactor即NioWorker,也是由它的selector充当reactor的作用,构造NioServerSocketChannelFactory时传入的executor即上图的thread pool,worker threads由executor负责管理生命周期。
③ queued tasks所属的队列即NioServerBoss和NioWorker的taskQueue,selector和taskQueue是protected的,即每个NioServerBoss和NioWorker都会持有一个。
④ 通过第三点,可以看出Netty实际的结构是一个main reactor,多个sub reactor(由线程池线程数目配置决定)。
2. worker与IOT的关系
worker---taskQueue/selector---IOT
一个worker持有一个taskQueue和selector,而IOT的主要逻辑就是processTaskQueue和process,即分别处理poll taskQueue,执行任务,以及操作selector的selection key,对触发的事件进行处理。所以它们之间实际上是通过taskQueue和selector产生联系的。并且每个NioWorker一个taskQueue和selector,可以避免多个worker争用同一个taskQueue和selector的情况,也就避免了做同步(虽然selector是线程安全的,但是selection key不是,如果多个worker共用则无法避免做同步)
3. 线程启动流程
① 前面2篇提到在new NioServerSocketChannelFactory时,启动了boss和IOT。通过看源码,IOT实际上是在每次new NioWorker时,通过传入的work executor,启动了一个IOT,然后该IOT开始循环处理taskQueue
和selector,当一组NioWorker构造完成时,一组IOT也一起启动了。
② worker构造完成后,当NioServerBoss accept到新连接请求时,boss需要负责构造新channel,并分配给某一个worker。具体做法是accept时,从WorkerPool中取一个worker来new channel,取的时候是通过模算法来取的:
public E nextWorker() {
return (E) workers[Math.abs(workerIndex.getAndIncrement() % workers.length)];
}
之前看到这里的时候,忽略了取模操作,所以感觉好像channel和worker的关系是一对一,即一个channel一个线程,当时看到这里怎么也没想通,因为明明使用的是nio模式,不可能一对一的,后来细看才发现原来看丢了。。。。
取模操作产生的效果就是在new channel时,依次从数组里取worker,然后将channel绑定到该worker上,所以实际上,channel和worker的关系是多对一,channel是以worker分组的,一个worker负责一组channel的IO。
netty使用多个sub reactor去分别分组处理channel,我理解的这样设计的好处:
1) 首先就是前面提到的不用同步,避免多个IOT争用一个sub reactor。
2) 可扩展性更灵活,因为如果用多个IOT,一个taskQueue或selector,必然会用竞争,当服务端需要更多IOT来处理高并发的时候,IOT越多,竞争越激烈,竞争的开销远远超过多IOT带来的性能提升,所以虽然理论上多IOT,单taskQueue或selector也具备扩展性,但是实际情况下并没有可操作性。
③ 获取worker后,使用worker,acceptedSocket和NioServerSocketChannel去new NioAcceptedSocketChannel,将它们绑定到新channel(该channel可以理解为与物理连接对应的、位于netty层的逻辑连接)。然后投递一个注册interest事件的任务到worker.taskQueue上,由IOT负责poll并执行。
4. 由线程模型带来的特性
① messageReceived太耗时造成IOT无响应
服务端select到OP_READ后,读数据,然后fireMessageReceived,因此messageReceived方法是IOT调用的,当其中的操作太耗时时(比如大量发送消息,IOT会逐条加锁channel.writeLock,循环发送消息,发完才返回进入下一个IOT循环),IOT忙于处理messageReceived,不能响应select,导致所有绑定到该IOT的channel都无响应,所以耗时的操作必须另起业务线程。
② 业务线程(用户线程,UT)大量发送消息
因为另起了业务线程,IOT可以及时返回以进行下一次processTaskQueue和process。由于写的数据和写的操作分离的设计(writeFromUserCode中先offer到writeBufferQueue,再投递channel.writeTask给IOT),造成当UT投递writeTask和offer数据的速度大于IOT的发送速度时(比如,绑定到同一IOT的channel太多,IOT忙于处理其它channel时),此时假设已有n条数据offer到了writeBufferQueue,当前正在执行第i个任务(i+1<n),第i+1个任务仍在taskQueue中(因为任务i还未处理完),使得writeTaskInTaskQueue未被置false(该标志表示可以继续投递writeTask,仅限writeTask,其它任务类型不清楚),i+1以后的任务无法投递进去,但是此时writeBufferQueue中已有n条数据,即相当于后面的写操作的数据先于写任务被offer,写的数据和写的任务并非一一对应,同时被投递的。在这种情况下,任务i会一直poll writeBufferQueue然后发送,直到writeBufferQueue为空(AbstractNioWorker.write0就是这样实现的),造成任务i”帮忙”把后面任务的数据都发了。然后执行任务i+1:加锁channel.writeLock--->poll writeBufferQueue为空,退出循环--->fireWriteComplete--->解锁channel.writeLock,该步骤实际上并无任何数据发送,但白白多锁了一次writeLock(还fire了一次complete事件,fire一次应该关系不大,暂不考虑)。并且任务i+2也会同样多加锁一次,直到任务n+1(在投递成功的writeTask数赶上offer的数据之前,这种状况会一直持续,在任务i执行完后,可能会有n以后的数据offer进去,这些新增的数据终究会在某些任务中被发送,但中间的任务应该始终还是会有些被“浪费”掉,可能呈现出间歇性的特征;如果要完全不浪费,即一条数据由对应的写任务发送,应该是在UT和IOT处理速度比较平衡的情况下才会发生吧)。在极端情况下,可能任务1就把后面所有任务的数据都发了,这样的话相当于后面的任务全都“废了”,并且前面的写任务“帮忙”发的数据条数越多,IOT不响应selector的概率越大(因为IOT被困在write0的循环里了,直到queue取空才会退出)。
这种情况是我在调试的时候发现的,然后手工干预线程调度,进一步确定的情况,但是在运行环境中会不会存在这种情况,我还不是很确定,目前想到的就是UT入队速度远大于IOT的处理速度时,这种情况我只能猜测是在IOT绑定的channel特别多时可能会发生(如果是linux,selector能处理的连接数有限制,只能是单个进程能打开的文件描述符个数上限值,默认好像是1024),不知道并发达到多大的时候会出现这种情况,不知道1024够不够资格去触发。。。所以大家看了第二点,帮我想想有没哪里没分析对。
我又想了一下:其实也不算白白多加一次锁和多fire一次,在理想情况下,write多少次就应该锁多少次writeLock,如果连这个都想省掉,是不是太夸张了,不知道这算不算一个问题,或者说有没有优化的必要和可能性,求指点啊。。。
本人辛苦分析、码字,请尊重他人劳动成果,转载不注明出处的诅咒你当一辈子一线搬砖工,嘿嘿~
欢迎讨论、指正~~
下篇预告:客户端连接流程分析
相关推荐
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. 客户端连接辅助类...
6)Netty源码分析 ByteBuf工作原理 Channel, Unsafe ChannelPipline, ChannelHandler EventLoop, EventLoopGroup Future, Promise 7) Netty逻辑架构 8)Netty中的多线程编程 9)Netty与RPC 10)Netty的可靠性 ...
72_Netty线程模型深度解读与架构设计原则 73_Netty底层架构系统总结与应用实践 74_Netty对于异步读写操作的架构思想与观察者模式的重要应用 75_适配器模式与模板方法模式在入站处理器中的应用 76_Netty项目开发过程...
第72讲:Netty线程模型深度解读与架构设计原则 第73讲:Netty底层架构系统总结与应用实践 第74讲:Netty对于异步读写操作的架构思想与观察者模式的重要应用 第75讲:适配器模式与模板方法模式在入站处理器中的...
04、第四课netty线程模型源码分析(一) 05、第五课netty线程模型源码分析(二) 06、第六课netty5案例学习 07、第七课netty学习之心跳 08、第八课protocol buff学习 09.第九课自定义序列化协议之自定义序列化协议 ...
52_NioEventLoopGroup源码分析与线程数设定 53_Netty对Executor的实现机制源码分析 54_Netty服务端初始化过程与反射在其中的应用分析 55_Netty提供的Future与ChannelFuture优势分析与源码讲解 56_Netty服务器地址...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...
Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...
JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...