您的位置 首页 > 德语词汇

jetty是什么意思(中的线程优化思路)

大家好,jetty是什么意思相信很多的网友都不是很明白,包括中的线程优化思路也是一样,不过没有关系,接下来就来为大家分享关于jetty是什么意思和中的线程优化思路的一些知识点,大家可以关注收藏,免得下次来找不到哦,下面我们开始吧!

作者:vivo互联网服务器团队-WangKe

本文介绍了Jetty中ManagedSelector和ExecutionStrategy的设计实现,通过与原生select调用的对比揭示了Jetty的线程优化思路。Jetty设计了一个自适应的线程执行策略(EatWhatYouKill),在不出现线程饥饿的情况下尽量用同一个线程侦测I/O事件和处理I/O事件,充分利用了CPU缓存并减少了线程切换的开销。这种优化思路对于有大量I/O操作场景下的性能优化具有一定的借鉴意义。

jetty是什么意思(中的线程优化思路)

Jetty跟Tomcat一样是一种Web容器,它的总体架构设计如下:

Jetty总体上由一系列Connector、一系列Handler和一个ThreadPool组成。

Connector也就是Jetty的连接器组件,相比较Tomcat的连接器,Jetty的连接器在设计上有自己的特点。

Jetty的Connector支持NIO通信模型,NIO模型中的主角是Selector,Jetty在Java原生Selector的基础上封装了自己的Selector:ManagedSelector。

常规的NIO编程思路是将I/O事件的侦测和请求的处理分别用不同的线程处理。

这个过程有两个线程在干活:一个是I/O事件检测线程、一个是I/O事件处理线程。

这两个线程是"生产者"和"消费者"的关系。

将两个工作用不同的线程处理,好处是它们互不干扰和阻塞对方。

当Selector检测读就绪事件时,数据已经被拷贝到内核中的缓存了,同时CPU的缓存中也有这些数据了。

这时当应用程序去读这些数据时,如果用另一个线程去读,很有可能这个读线程使用另一个CPU核,而不是之前那个检测数据就绪的CPU核。

这样CPU缓存中的数据就用不上了,并且线程切换也需要开销。

2.2Jetty中的ManagedSelector实现

Jetty的Connector将I/O事件的生产和消费放到同一个线程处理。

如果执行过程中线程不阻塞,操作系统会用同一个CPU核来执行这两个任务,这样既能充分利用CPU缓存,又可以减少线程上下文切换的开销。

ManagedSelector本质上是一个Selector,负责I/O事件的检测和分发。

为了方便使用,Jetty在Java原生Selector的基础上做了一些扩展,它的成员变量如下:

publicclassManagedSelectorextendsContainerLifeCycleimplementsDumpable\n{\n//原子变量,表明当前的ManagedSelector是否已经启动\nprivatefinalAtomicBoolean_started=newAtomicBoolean(false);\n\n//表明是否阻塞在select调用上\nprivateboolean_selecting=false;\n\n//管理器的引用,SelectorManager管理若干ManagedSelector的生命周期\nprivatefinalSelectorManager_selectorManager;\n\n//ManagedSelector的id\nprivatefinalint_id;\n\n//关键的执行策略,生产者和消费者是否在同一个线程处理由它决定\nprivatefinalExecutionStrategy_strategy;\n\n//Java原生的Selector\nprivateSelector_selector;\n\n//"Selector更新任务"队列\nprivateDeque<SelectorUpdate>_updates=newArrayDeque<>();\nprivateDeque<SelectorUpdate>_updateable=newArrayDeque<>();\n\n...\n}

2.2.1SelectorUpdate接口

为什么需要一个"Selector更新任务"队列呢?

对于Selector的用户来说,我们对Selector的操作无非是将Channel注册到Selector或者告诉Selector我对什么I/O事件感兴趣。

这些操作其实就是对Selector状态的更新,Jetty把这些操作抽象成SelectorUpdate接口。

/**\n*Aselectorupdatetobedonewhentheselectorhasbeenwoken.\n*/\npublicinterfaceSelectorUpdate\n{\nvoidupdate(Selectorselector);\n}

这意味着不能直接操作ManagedSelector中的Selector,而是需要向ManagedSelector提交一个任务类。

这个类需要实现SelectorUpdate接口的update方法,在update方法中定义要对

比如Connector中的Endpoint组件对读就绪事件感兴趣。

它就向ManagedSelector提交了一个内部任务类

ManagedSelector.SelectorUpdate:

_selector.submit(_updateKeyAction);

这个_updateKeyAction就是一个

SelectorUpdate实例,它的update方法实现如下:

privatefinalManagedSelector.SelectorUpdate_updateKeyAction=newManagedSelector.SelectorUpdate()\n{\n@Override\npublicvoidupdate(Selectorselector)\n{\n//这里的updateKey其实就是调用了SelectionKey.interestOps(OP_READ);\nupdateKey();\n}\n};

在update方法里,调用了SelectionKey类的interestOps方法,传入的参数是OP_READ,意思是我对这个Channel上的读就绪事件感兴趣。

2.2.2Selectable接口

上面有了update方法,那谁来执行这些update呢,答案是ManagedSelector自己。

它在一个死循环里拉取这些SelectorUpdate任务逐个执行。

I/O事件到达时,ManagedSelector通过一个任务类接口(Selectable接口)来确定由哪个函数处理这个事件。

publicinterfaceSelectable\n{\n//当某一个Channel的I/O事件就绪后,ManagedSelector会调用的回调函数\nRunnableonSelected();\n\n//当所有事件处理完了之后ManagedSelector会调的回调函数\nvoidupdateKey();\n}

Selectable接口的onSelected()方法返回一个Runnable,这个Runnable就是I/O事件就绪时相应的处理逻辑。

ManagedSelector在检测到某个Channel上的I/O事件就绪时,ManagedSelector调用这个Channel所绑定的类的onSelected方法来拿到一个Runnable。

然后把Runnable扔给线程池去执行。

3.1Jetty中的ExecutionStrategy实现

前面介绍了ManagedSelector的使用交互:

那么ManagedSelector如何统一管理和维护用户注册的Channel集合呢,答案是

这个接口将具体任务的生产委托给内部接口Producer,而在自己的produce方法里实现具体执行逻辑。

这个Runnable的任务可以由当前线程执行,也可以放到新线程中执行。

publicinterfaceExecutionStrategy\n{\n//只在HTTP2中用到的一个方法,暂时忽略\npublicvoiddispatch();\n\n//实现具体执行策略,任务生产出来后可能由当前线程执行,也可能由新线程来执行\npublicvoidproduce();\n\n//任务的生产委托给Producer内部接口\npublicinterfaceProducer\n{\n//生产一个Runnable(任务)\nRunnableproduce();\n}\n}

实现Produce接口生产任务,一旦任务生产出来,ExecutionStrategy会负责执行这个任务。

privateclassSelectorProducerimplementsExecutionStrategy.Producer\n{\nprivateSet<SelectionKey>_keys=Collections.emptySet();\nprivateIterator<SelectionKey>_cursor=Collections.emptyIterator();\n\n@Override\npublicRunnableproduce()\n{\nwhile(true)\n{\n//如果Channel集合中有I/O事件就绪,调用前面提到的Selectable接口获取Runnable,直接返回给ExecutionStrategy去处理\nRunnabletask=processSelected();\nif(task!=null)\nreturntask;\n\n//如果没有I/O事件就绪,就干点杂活,看看有没有客户提交了更新Selector的任务,就是上面提到的SelectorUpdate任务类。\nprocessUpdates();\nupdateKeys();\n\n//继续执行select方法,侦测I/O就绪事件\nif(!select())\nreturnnull;\n}\n}\n}

SelectorProducer是ManagedSelector的内部类。

SelectorProducer实现了ExecutionStrategy中的Producer接口中的produce方法,需要向ExecutionStrategy返回一个Runnable。

在produce方法中SelectorProducer主要干了三件事:

3.2.1ProduceConsume(PC)线程执行策略

任务生产者自己依次生产和执行任务,对应到NIO通信模型就是用一个线程来侦测和处理一个ManagedSelector上的所有的I/O事件。

后面的I/O事件要等待前面的I/O事件处理完,效率明显不高。

图中,绿色代表生产一个任务,蓝色代表执行这个任务,下同。

3.2.2ProduceExecuteConsume(PEC)线程执行策略

任务生产者开启新线程来执行任务,这是典型的I/O事件侦测和处理用不同的线程来处理。

缺点是不能利用CPU缓存,并且线程切换成本高。

图中,棕色代表线程切换,下同。

3.2.3ExecuteProduceConsume(EPC)线程执行策略

任务生产者自己运行任务,这种方式可能会新建一个新的线程来继续生产和执行任务。

它的优点是能利用CPU缓存,但是潜在的问题是如果处理I/O事件的业务代码执行时间过长,会导致线程大量阻塞和线程饥饿。

3.2.4EatWhatYouKill(EWYK)改良线程执行策略

这是Jetty对ExecuteProduceConsume策略的改良,在线程池线程充足的情况下等同于ExecuteProduceConsume;

当系统比较忙线程不够时,切换成

这么做的原因是:

ExecuteProduceConsume是在同一线程执行I/O事件的生产和消费,它使用的线程来自Jetty全局的线程池,这些线程有可能被业务代码阻塞,如果阻塞的多了,全局线程池中线程自然就不够用了,最坏的情况是连I/O事件的侦测都没有线程可用了,会导致Connector拒绝浏览器请求。

于是Jetty做了一个优化

ProduceExecuteConsume策略,I/O侦测用专门的线程处理,I/O事件的处理扔给线程池处理,其实就是放到线程池的队列里慢慢处理。

本文基于Jetty-9介绍了ManagedSelector和ExecutionStrategy的设计实现,介绍了PC、PEC、EPC三种线程执行策略的差异,从Jetty对线程执行策略的改良操作中可以看出,Jetty的线程执行策略会优先使用EPC使得生产和消费任务能够在同一个线程上运行,这样做可以充分利用热缓存,避免调度延迟。

这给我们做性能优化也提供了一些思路:

来源:微信公众号:vivo互联网技术

出处:https://mp.weixin.qq.com/s/twCo4JT_dSFrpXr1_5eMgw

jetty是什么意思的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于中的线程优化思路、jetty是什么意思的信息别忘了在本站进行查找哦。

本站涵盖的内容、图片、视频等数据,部分未能与原作者取得联系。若涉及版权问题,请及时通知我们并提供相关证明材料,我们将及时予以删除!谢谢大家的理解与支持!

Copyright © 2023