`
vinceall
  • 浏览: 10262 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

Netty源码分析3---服务端线程模型

 
阅读更多

只看了服务端的线程结构,客户端还没看,不知道是不是一样,所以这篇就叫服务端线程模型吧,以后看了客户端再修改。术语和第二篇一样,不重复写了。

 

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,如果连这个都想省掉,是不是太夸张了,不知道这算不算一个问题,或者说有没有优化的必要和可能性,求指点啊。。。

 

本人辛苦分析、码字,请尊重他人劳动成果,转载不注明出处的诅咒你当一辈子一线搬砖工,嘿嘿~

欢迎讨论、指正~~

 

下篇预告:客户端连接流程分析

  • 大小: 69.1 KB
1
1
分享到:
评论

相关推荐

    高清Netty5.0架构剖析和源码解读

    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. 客户端连接辅助类...

    基于javatcpsocket通信的拆包和装包源码-NettyTree:网状树

    6)Netty源码分析 ByteBuf工作原理 Channel, Unsafe ChannelPipline, ChannelHandler EventLoop, EventLoopGroup Future, Promise 7) Netty逻辑架构 8)Netty中的多线程编程 9)Netty与RPC 10)Netty的可靠性 ...

    精通并发与netty视频教程(2018)视频教程

    72_Netty线程模型深度解读与架构设计原则 73_Netty底层架构系统总结与应用实践 74_Netty对于异步读写操作的架构思想与观察者模式的重要应用 75_适配器模式与模板方法模式在入站处理器中的应用 76_Netty项目开发过程...

    精通并发与netty 无加密视频

    第72讲:Netty线程模型深度解读与架构设计原则 第73讲:Netty底层架构系统总结与应用实践 第74讲:Netty对于异步读写操作的架构思想与观察者模式的重要应用 第75讲:适配器模式与模板方法模式在入站处理器中的...

    Java视频教程 Java游戏服务器端开发 Netty NIO AIO Mina视频教程

    04、第四课netty线程模型源码分析(一) 05、第五课netty线程模型源码分析(二) 06、第六课netty5案例学习 07、第七课netty学习之心跳 08、第八课protocol buff学习 09.第九课自定义序列化协议之自定义序列化协议 ...

    精通并发与 netty 视频教程(2018)视频教程

    52_NioEventLoopGroup源码分析与线程数设定 53_Netty对Executor的实现机制源码分析 54_Netty服务端初始化过程与反射在其中的应用分析 55_Netty提供的Future与ChannelFuture优势分析与源码讲解 56_Netty服务器地址...

    java开源包3

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    JAVA上百实例源码以及开源项目源代码

     Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...

    JAVA上百实例源码以及开源项目

     Java 3DMenu 界面源码,有人说用到游戏中不错,其实平时我信编写Java应用程序时候也能用到吧,不一定非要局限于游戏吧,RES、SRC资源都有,都在压缩包内。 Java zip压缩包查看程序源码 1个目标文件 摘要:Java源码...

    java开源包8

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包1

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包10

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包11

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包2

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包6

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包5

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包4

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

    java开源包7

    JCarder 是一个用来查找多线程应用程序中一些潜在的死锁,通过对 Java 字节码的动态分析来完成死锁分析。 Java的Flash解析、生成器 jActionScript jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。...

Global site tag (gtag.js) - Google Analytics