欢迎大家注意github.com/hsfxuebao。我希望这对每个人都会有所帮助。如果您认为有可能
Netty框架的主线程是I/O线程。线程模型设计的设计确定了系统吞吐量,并发和安全性的质量属性。Netty的线程模型经过精心设计,这不仅可以改善框架的并发性能,而且还可以在很大程度上避免锁。
通常,您想到的第一件事是经典。尽管不同的NIO框架在反应堆模式的实现方面存在差异,但它仍然遵循反应器的基本线程模型。反应器单线读取模型,反应器多线程模型以及主和反应器多线程模型。有关详细信息,请参阅IO系列4-浅浅的浅反应堆和ProCARCTOR模式。我不会在此处介绍详细信息。
Netty的线程模型不是静态的,它实际上取决于用户的启动参数配置。通过设置不同的启动参数,Netty可以支持反应堆单线线模型,多线程模型以及Maintor -Stor -Storsist -consist -consist -consist in Multi -Line模型。
让我们快速通过示意图理解Netty的线程模型。
您可以通过Netty服务器了解其线程模型以启动代码:
服务器启动后,创建了两个NioEventloopGroup,实际上是两个独立的反应器线程池。一个用于接收客户端的TCP连接,另一个用于处理I/O相关的读取和写入操作,或执行系统任务,定时任务,ETC。
较低的木偶直接给出配置实例:
i i i i i i niioEventloopGroup的顶部是表的第一个the,一个单个线程池。一些朋友可能有疑问:我记得在服务器端启动Netty程序时,您需要设置BossGroup和WorkerGroup。实际上,这是非常简单的,ServerBootstrap重写了组?方法:
连接到所有后续客户端的IO操作是在线程中处理的。然后,反应堆的相应线程模型,当我们以这种方式设置NioEventlooproup时,它等效于反应堆单线线模型。
同样,首先给出源代码:
Bossground是多个线程,因此对应于反应器线程模型,因此NioEventloopGroup实际上是反应堆多线模型。
吡啶的实施如下:
通过,这种灵活的配置方法可以最大化不同用户的个性化。
为了尽可能提高性能,Netty在许多地方没有锁定设计,例如I/O线程中的串行操作,以避免由多线程竞争引起的性能下降问题。,串行设计似乎在CPU利用率和并发不足中不高。但是,通过调整NIO螺纹池的线程参数,可以在并行操作中启动多个序列化线程。这种本地解锁的串行线程设计比一个队列 - 多个工作线程模型要好。
设计原理如图所示:
在阅读了Netty的Nioeventloop的消息后,请致电ChannelPipeline的FireChannelRead(Object MSG)。只要用户不积极切换线程,它始终称为NioEventloop称为用户的处理程序。该串行处理方法避免使用竞争是由多线程操作引起的,从性能角度来看,它是最佳的。
Netty多线程编程的最佳实践是如下。
(1)为。
(2)尽量不要在ChannelHandler中启动用户线程(除了解码将POJO消息发送到后端 - 端业务线程之外)。
(3)应在NIO线程调用的解码器中执行解码。请勿切换到用户线程以完成消息的解码。
(4)如果业务逻辑操作非常简单,并且没有复杂的业务逻辑计算,则不会导致线程被磁盘操作,数据库操作和网络操作阻止到用户线程。
(5)如果业务逻辑处理很复杂,请在NIO线程上完成。建议将解码的POJO消息作为任务包装并将其分配到业务线程池中,以确保尽快释放NIO线程,并处理其他I/O操作的其他i。
较低的?分析了nioEventloopgroup类的实例化过程。
NioEventloopGroup类结构。赋予类图:对于NioEventloopGroup核的继承关系:NioEventloopGroup-> MultithReadeventLoopGroup-> MultIthReadeventLoopGroup-> MultIthReadeeDeTeentExecutorGroup niioEventLopGroup fornioeventLopGroup?分析。
这个实例化的过程是什么?
如果未指定线程的数字参数的构造函数,则将默认值设置为0(但将在后方的构造函数中进行判断。如果将其设置为0,则将初始化为2*的数字。中央处理器);
然后调整?:
此?设置了NioEventloopGroup螺纹池中每个线程设备的默认值(此?设置为null,并且将在后部的构造函数中判断。
再次休息:
这个?存在于JDK的NIO互动中。此?设置了线程池的SelectorProder并返回。
然后调整?:
在这个沉重的构造函数中,通过了默认选择策略。
该?是多读雷德鲁普组的构造函数,它还添加了线程的拒绝策略。
构造函数被定义为潜在的产品,只能在Nioeventloopgroup中调整表,该表在层的层面受到保护。该?审判螺纹的数量,并将其设置为此常数。该常数如下:实际上,它实际上是要将default_event_loop_threads分配给CPU核编号* 2;
下一步是MultIthReadeventExecutorGroup的构造函数。
该构造函数通过参数DefaulteenteentexecutorChoserFactory.incance传递。通过此Eventloop选择器??可以在线程池中实例化genericeVentexecutorChooser的类别,选择合适的Eventloop线程。
然后是多inthreadeventexecutorgroup的构造函数的构造函数:将构造函数调谐到此?以继续传递曲调的终点。由于长长的构造函数?,我将删除一些验证,并且不重要地认为这并不重要。编码,仅保留核代码:
总结上部和上部始始(3)的初始化步骤:
该线程池的初始化已经结束。基本上,该部分涉及Netty线程池的内容,并且不涉及通道和通道私线和ChannelHandler的内容。
Netty的NioEventloop不是纯的I/O线程。除了负责I/O的阅读和写作外,它还考虑了以下两种任务:
正是因为Nioeventloop具有多种责任,因此其实施是特殊的。这不是一个简单的运行。
它实现了EventLoop接口,EventExeCutorGroup接口和计划ExecutorService接口。正是由于这种设计导致Nioeventloop及其父函数非常复杂。
特定的螺纹绑定及其生命周期中,绑定线将不会再次变化。
NioEventloop的类层次结构图仍然很复杂,但是我们只需要注意一个重要的一点。
在Abstractschedeventexecutor中,Netty实现了NioEventloop的时间表函数,也就是说,我们可以通过调整NioEventloop实例的Schedule()方法来运输某些时间任务。任务队列,并安排NioEventloop的安排。
NioEventloop的实例基本上是niofeventloopgroup.newchild()中的。
从上面的??函数w n neioEventloop(),分析了分析过程。
(1)NioEventloop首次调整的构造函数:
相同的nioeeventloopgroup。
还要注意的是,selectorProvider构造函数构造参数通过selectorProvider.provider()的nioeventloopgroup的构造函数(); single -case selectorProvider;SelectorProvider。
(2)在讲话后,核材料是Singlethreadeventloop的构造函数:
除了Singlethreadeveentexecutor的构造函数外,此?还与tailtasks的变量实例化;
Singlethreadeventloop中的TailTasks的定义如下:
队列的数量maxpendingTasks参数默认值为singlethreadeventloop.default_max_pending_task,实际上是integer.max_value;对于新队列,它实际上是LinkedBlockingqueue的行队列。
(3)查看Singlethreadeventexecutor的构造函数:
v这是NioEventloop的实例化过程。
前者?分析了事件循环集团和EventLoop。然后是一个问题。我们知道Eventloop实际上对应于线程。那么这个eventloop什么时候开始?
我们已经知道在了的前面,NioEventloop是一个单身人士,因此Nioeventloop的启动实际上是由Nioeeeventloop绑定的本地Java线程的启动。在Nioeventloop的初步分析中,我们知道直到Singlethreadeventexececutor,我们才知道,我们已经通过了Singlethreadeventeventexececutor,我们才能使用。?线程经销商执行人。我认为线程的开始是通过此线程开始的。
在Singlethreadeventexecutor类中,有一个共同的属性。该线程是绑定到NioEventloop的本地Java线程。LET查看此线程何时初始化并启动。
通过定位线程变量,发现在函数中,有一个??代码:
除了这一地面外,发现没有其他地面木偶将线程实例化到??????????????
换句话说,执行程序中的线程 - 线程线程??,这意味着将执行程序提交的任务创建的线程分配给线程对象。可以看出,线程的启动实际上在这里。
通过对功能的音调关系的连续分析,顶部的音调是singlethreadeventexecutor.execute(可运行的任务)
换句话说,再次调整了execute()函数,也就是说,nioeventloop被激活。LET分析初始??????????????????????????????????????????Nioeventloop。
在分析NioEventloop的启动过程之前,让我们看一下Eventloop与渠道的关联?
在Netty中,他们的关联过程如下:
从上图,我们可以看到,当称为“ AbstractChannel#AbstractUnsafe.register”时,通道和Eventloop的相关性就完成了。注册如下:
在AbstractChannel#AbstractUnsafe.Register中,将EventLoop分配给AbstractChannel中的EventLoop字段。在这里,Eventloop和频道的关联过程已完成。
基于先前的分析,我们当前的任务是找出我第一次致电singlethreadeventexecutor.execute()。
注意读者可能已经注意到,当我们与Eventloop和渠道的关联相关时,我们提到,在注册频道的过程中,我们将在AbstractChannel#AbstractunSafe.register中调用Eventloop.ecute。频道。注册代码的执行,AbstractChannel#AbstractUnsafe.Register部分代码:如下:
显然,从bootstrap.bind方法到AbstractChannel#AbstractUnsafe.Register方法,整个代码在主线程中都运行,因此上面的Eventloop.ineventloop()是错误的。执行。EventLoop是NioEventloop的一个实例,NioEventloop不会实现执行方法。所以
我们已经分析了InEventloop == false,因此它执行到Else分支,在这里我们调用startthread()方法以启动Singlethreadeveenteveentexecutor的内部Java本地线程。
总而言之,当第一次调用eventloop.ectute时,将触发startThread()的呼叫,这导致与Eventloop相对应的Java线程的开始。
在Eventloop和Channel的关联部分中补充了顺序图后,我们获得了Eventloop启动过程的定时图:
在上述分析之后,我们发现了入口以启动NioEventloop线程。这是分析Nioeventloop启动后如何实现所谓事件周期机制的方法?
也就是说,从源代码开始,我将直接删除一些不重要的源代码,仅保留核心源代码
上面的功能实际上是Thassqueue队列中的任务任务。
看看如何实现dostartthread()。此功能仍然更加复杂。我只拿出核心业务逻辑:
从上面可以看出,核心是称呼这是一个抽象函数。此功能在NioEventloop中实现。下面仍然只有可以解释核心思想的核心源代码。我删除了不重要的源代码。
上述功能中的死周期是(;;)是NioEventloop事件周期执行机制。死周期中的简单业务是:
详细说明了以下过程:两个步骤:IO事件,处理IO事件。
首先,在运行()方法中,第一步是调用hastasks()方法以确定当前任务队列中是否有任务:
此方法非常简单,只需检查Thesqueue是否为空。至于什么是Taskqueue实际上是一系列事件的任务执行的任务列表。关于Taskqueue,我们暂时不会显示它。
1)当Taskqueue不是空的时,Hastasks()将返回true,然后将执行实现,并将调用GET()方法并立即执行当前IO事件的数量。如果有一个IO事件,则在Switch语句中进行,则将直接执行默认值,并且Switch语句将直接跳跃。如果不存在,它将返回0到继续并忽略周期。
2)当Taskqueue为空时,它将返回SelectStrategy.Select。对于Switch Case语句,它是执行Select()函数以阻止等待IO事件。
在NioEventloop.run()方法中,第一步是通过SELECT/SELECTNOW调用当前IO事件。然后,当发生IO事件时,第二步是处理这些IO事件。首先,让我们首先来。查看Nioeventloop.lun(核心部分)中周期的其余部分:
在上面列出的代码中,有两个关键调用。首先是根据字面意义,我们可以猜测必须查询此方法。我们还可以看出,它的功能是在任务中运行任务。
这里也有一个非常有趣的地方,也就是说。什么是)。例如,Ioratio默认值50,这意味着IO操作和执行任务的占用线程执行时间比例为1:1。执行任务可以轻松计算。
当我们设置IATER = 70时,这意味着IO运行时间的比例为70%,也就是说,假设总周期为100ms,则根据公式,我们知道ProcessElectKeys()方法调用大约需要70ms。(这是ISIO的时间消耗),而RunallTasks()需要大约30ms(即执行任务时间)。
当Ioratio为100时,Netty不考虑IO时间的比例,而是调用ProcessSelectKeys()和RunMallTasks();当Ioratio不是100时,它将被执行到Else分支。接下来,执行时间(即执行IO操作的时间消耗),然后根据公式执行任务和计算任务所占据的时间,并且然后将其用作参数来调用runallTasks()。
让我们首先在此处分析ProcessSelectKeys()方法。源代码如下:
在此方法中,它将根据SelectedKeys字段为空,以及ProcessElectKeysOptimized或ProcessSseSelectedKeySplain。调用JVM平台时,SelectedKeys Field方法与JVM平台不同。实际上,ProcessSselectedKeysoptimind方法没有太大不同。例如,为简单起见,我们可以使用过程代码作为示例来分析源代码的工作流程。
实际上,不要看很多代码,但是
要注意的另一件事是,我们可以调用selectkey.attach(object)为selectkey设置附加字段,然后通过object natctedobj = selectekey.attachment.atchment()。这个对象是什么?它在哪里设置?让我们回想一下选择器中的Socketchannel如何注册:在客户端的频道注册过程中,这是可能的。在以下呼叫链中:
最终的AbstractNioChannel.doregister方法将调用Socketchannel.Register方法将Socketchannel注册到指定的选择器:
请特别注意寄存器的第三个参数。此参数设置为设置SelectionKey的附加对象,该对象与调用SelectKey.attach(Object)相同。调用寄存器的第三个参数就是这样。它实际上是NioSocketchannel。然后在这里很清楚。当我们将Socketchannel注册到选择器中时,我们将与Socketchannel相对应的Niosocketchannel通过其他字段将与Socketchannel相对应的niosocketchannel。然后返回到ProcessSelectKeysoptimizatized方法,当我们获得其他对象时,此后,我们致电ProcessSelectKey来处理此IO事件:
processSelectedKey方法源代码如下:
这个代码熟悉吗?这是Java Nio选择器的一组处理过程!ProcessSselectedKey处理了三个事件,即::
当准备就绪的IO事件是OP_READ时,代码将调用unsafe.read()方法,即:
在不安全的领域,我们已经与之进行了太多的谈话。分析引导程序时,我们通过强烈而丰富多彩的分析进行了分析。最后,我们确定这是一个Niosocketchannelunsafe实例。它负责频道的底部IO操作。我们可以使用Intellij Idea提供的实现功能来查找此方法的实现。最后,我们发现该方法未在Niosocketchannelunsafe中实现,而是由其父级Abstractniobytechannel实现的。其实施源代码如下:
read()源代码相对较长。为了长度,我删除一些代码,只留下中继。但是,我建议读者和朋友必须查看read()源代码,这对于理解Netty的Eventloop非常有帮助。上面上述。实际上总结了读取方法,可以将其视为以下工作:
关于前两个点没有什么可说的。第三点管道。FirechaneNelread读取器查看您是否看到微笑?无论如何,当我看到它时,我会看到它。firechanleam恰好是入站事件的起点。当调用pipeline.firein_evt()时,就会生成一个入站事件。该事件将流过头部 - > CustomContext->尾巴的方向。
调用Pipeline.firechnelread后,这是需要在ChannelPipeline中完成的工作。
OP_WRITE可以按以下方式编写事件代码。这里的代码相对简单,无需详细分析。
最后一个事件是OP_Connect,即TCP连接已建立了一个事件。
在OP_Connect事件的处理中,只完成了两件事:
,生成一个入站事件,通知管道中的各种处理程序TCP通道(即ChannelInboundHandler.ChannElactive方法将被调用)
顺便说一句,已经理解了整个NioEventloop的IO操作部分。在下一节中,我们必须关注Netty任务队列机制。
我们已经提到,在Netty中,NioEventloop通常需要承担两项任务。首先是处理IO操作作为IO线程;第二个是将任务作为任务线程处理。重点是分析NioEventloop的任务队列机制。
3.5.1.1普通可运行的任务NioEventloop在Singlethreadeventexecutor中继承,而SinglethreadeventExecutor具有一个排队任务标语字段,用于存储添加的添加。在Netty中,每个任务都使用Runnnle接口来实现Runnnle。
例如,当我们需要添加可运行的Thassqueue时,我们可以执行以下操作:
当调用发掘时,实际上被调用为singlethreadeventexecutor.execute()。它的实施如下:
添加任务的addTask方法的源代码如下:
因此,实际上,Taskqueue是存储要执行的任务的队列。
3.5.1.2计划任务除了通过执行添加普通可运行的任务外,我们还可以通过调用Eventloop.schedulexxx之类的方法来添加定时任务。
Eventloop中实现任务队列的功能由Super -Class Singlethreadeventexecutor实施,并且在Singlethreadevenexecutor的父类中实现了时间表函数的实现,即AbstractSchedeventExecutor。
在Abstractschedeventexecutor中,有计划的Taskqueue字段:
ScheduledTaskqueue是一个队列,其中存储的元素已sendiuledfuturetask。和计划的FutureTask。我们可以轻松猜测。这是时间表任务的摘要。
让我们看一下Abstractschedeventexecutor实施的时间表方法:
这是重载时间表之一。当传递运行时,它将被封装为ScheduledFutureTask对象。该对象将记录运行时,该对象将运行什么频率操作和其他信息。将继续调用另一个重载的时间表方法:
在此方法中,将添加计划的FutureTask对象中的对象。
当任务添加到Taskqueue时,如何通过Eventloop执行?
让我们回到NioEventloop.run()方法。在此方法中,将单独调用ProcessSelectedKeys()和RunallTasks()方法来处理IO事件和任务处理。LET查看中间的著名大厅。
RunallTasks方法具有两种重载方法,一种是无参数,另一个具有一个参数。首先,查看无参数RunallTasks:
我们之前提到过。EventLoop可以通过调用EventLoop.ecute将可运行的运行时间提交到该任务。您还可以通过致电Eventloop.schedule提交计划任务。
FetchFromScheduledTaskQueue()实际上将其取出并添加到ScheduledTaskqueue(即时间到达时间已达到的时间表任务),并将其添加到Thassqueue中,作为可执行的任务,等待安排执行。
接下来,RunallTasks()方法将继续从Taskqueue调用可执行任务,然后调用其Run()方法运行此任务。
请注意,由于EventLoop需要执行IO操作和任务,因此当我们调用Eventloop.ecute方法提交任务时,请勿提交时间 - 耗费任务,更不用说提交一些会导致阻塞的任务,否则会导致我们的IO ThreadCancant获得计划,影响整个程序的并发。
这就是为什么我们使用自己的线程池来隔离可能被阻止的业务的原因。
Netty学习和源代码分析GitHub地址
从进入到熟练视频教程(B)的Netty
Netty权威指南第二版
转:事件卢比组和事件卢比分析
原始:https://juejin.cn/post/7097537535320490021