深入拆解 Tomcat & Jetty
李号双
eBay 技术主管
38890 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 45 讲
开篇词 (1讲)
深入拆解 Tomcat & Jetty
15
15
1.0x
00:00/00:00
登录|注册

19 | 比较:Jetty的线程策略EatWhatYouKill

EatWhatYouKill
ExecuteProduceConsume
ProduceExecuteConsume
ProduceConsume
侦测I/O就绪事件
处理更新Selector的任务
获取Runnable
具体策略实现类
dispatch, produce
用于处理I/O事件就绪时的回调函数
用于提交对Selector状态的更新
_started, _selecting, _selectorManager, _id, _strategy
提高了吞吐量8倍
ManagedSelector将这些事件Queue起来由自己来统一处理
需要向ManagedSelector提交一个SelectorUpdate事件
减少了线程切换的开销
充分利用了CPU缓存
尽量用同一个线程侦测和处理I/O事件
三件事情
实现了ExecutionStrategy中的Producer接口
ExecutionStrategy接口
Selectable接口
SelectorUpdate接口
成员变量
CPU缓存中也有这些数据
数据已经被拷贝到内核中的缓存
有两个线程在干活,一个是I/O事件检测线程,另一个是I/O事件处理线程
将I/O事件的侦测和请求的处理分别用不同的线程处理
大胆尝试的线程策略 - EatWhatYouKill
ManagedSelector的使用者不能直接向它注册I/O事件
Jetty设计了EatWhatYouKill的线程策略
SelectorProducer
ManagedSelector
缺点
常规的NIO编程思路
使用自己的Selector - ManagedSelector
支持NIO通信模型
由一系列Connector、一系列Handler和一个ThreadPool组成
课后思考
本期精华
Jetty中的Selector编程
Selector编程的一般思路
Jetty的Connector
Jetty总体架构设计
Jetty的线程策略EatWhatYouKill

该思维导图由 AI 生成,仅供参考

我在前面的专栏里介绍了 Jetty 的总体架构设计,简单回顾一下,Jetty 总体上是由一系列 Connector、一系列 Handler 和一个 ThreadPool 组成,它们的关系如下图所示:
相比较 Tomcat 的连接器,Jetty 的 Connector 在设计上有自己的特点。Jetty 的 Connector 支持 NIO 通信模型,我们知道 NIO 模型中的主角就是 Selector,Jetty 在 Java 原生 Selector 的基础上封装了自己的 Selector,叫作 ManagedSelector。ManagedSelector 在线程策略方面做了大胆尝试,将 I/O 事件的侦测和处理放到同一个线程来处理,充分利用了 CPU 缓存并减少了线程上下文切换的开销。
具体的数字是,根据 Jetty 的官方测试,这种名为“EatWhatYouKill”的线程策略将吞吐量提高了 8 倍。你一定很好奇它是如何实现的吧,今天我们就来看一看这背后的原理是什么。

Selector 编程的一般思路

常规的 NIO 编程思路是,将 I/O 事件的侦测和请求的处理分别用不同的线程处理。具体过程是:
启动一个线程,在一个死循环里不断地调用 select 方法,检测 Channel 的 I/O 状态,一旦 I/O 事件达到,比如数据就绪,就把该 I/O 事件以及一些数据包装成一个 Runnable,将 Runnable 放到新线程中去处理。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Jetty的线程策略EatWhatYouKill采用了名为“EatWhatYouKill”的线程策略,将I/O事件的侦测和处理放到同一个线程来处理,充分利用了CPU缓存并减少了线程上下文切换的开销。根据Jetty的官方测试,这种策略将吞吐量提高了8倍。Jetty的Connector支持NIO通信模型,封装了自己的Selector,叫作ManagedSelector。Jetty的Connector做了一个大胆尝试,将I/O事件的生产和消费放到同一个线程来处理,以充分利用CPU缓存。为了实现这一策略,Jetty定义了ExecutionStrategy接口,并提供了几种具体策略实现类。其中,EatWhatYouKill策略是对ExecuteProduceConsume策略的改良,能在系统繁忙时切换成ProduceExecuteConsume策略,以避免全局线程池中的线程被业务代码阻塞,导致I/O事件侦测无线程可用的情况发生。Jetty的EatWhatYouKill线程策略通过将I/O事件的生产和消费放到同一个线程来处理,充分利用CPU缓存,提高了系统的吞吐量。同时,Jetty还提供了多种具体策略实现类,以适应不同系统繁忙程度下的线程处理需求。Jetty巧妙设计了EatWhatYouKill的线程策略,尽量用同一个线程侦测I/O事件和处理I/O事件,充分利用了CPU缓存,并减少了线程切换的开销。 ManagedSelector的使用者不能直接向它注册I/O事件,而是需要向ManagedSelector提交一个SelectorUpdate事件,ManagedSelector将这些事件Queue起来由自己来统一处理,这样做有利于统一处理I/O事件,提高系统效率。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入拆解 Tomcat & Jetty 》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(19)

  • 最新
  • 精选
  • QQ怪
    课后问题就有点像分布式服务为什么爱用消息中间件一样,一切都是为了解耦,服务类比线程,服务与服务可以直接通讯,线程与线程也可以直接通讯,服务有时候会比较忙或者挂掉了,会导致该请求消息丢失,线程与线程之间的上下文切换同样会带来很大的性能消耗,如果此时的线程池没有多余的线程使用,可能会导致线程积压致使cpu飙高,使用队列能够有效平衡消费者和生产者之间的压力,达到高效率执行。

    作者回复: 是的,还有个原因是各个Process线程直接改变Poller的状态需要加锁,将Update的操作封装成任务放到队列串行执行无需加锁

    2019-06-23
    21
  • -W.LI-
    老实好! 检测到读就绪的时候数据已被拷贝到了内核缓存中。CPU的缓存中也有这些数据。 这句话怎么理解啊,CPU的缓存说的是高速缓存么?然后内核缓存是什么呢?这方面的知识需要看啥书补啊(是操作系统么?)?IO模型,数据需要拷贝的次数我一点搞不懂

    作者回复: 内核缓存是指,一个进程的虚拟地址地址空间分为用户空间和内核空间,我在22答疑篇会详细说说,这些都是操作系统的知识。

    2019-06-24
    3
  • 张德
    李老师能否讲一下怎么调试源码 怎么搭建调试源码的环境 就比如说拿Jetty举例

    作者回复: 用嵌入式的方式启动jetty,引入jetty jar包就行

    2019-06-25
    2
  • 802.11
    老师,在NIO2中服务器端的几个概念不是很清晰,比如WindowsAsynchronousChannelProvider,Invoker等。如果需要系统了解这几个概念是怎么串起来的,怎么办呢。

    作者回复: 建议阅读JVM底层源码

    2019-06-23
    2
  • -W.LI-
    老师好!我有个问题,我是先把学校教的计算机基础的书操作系统,计算机组成原理这些再看一遍补基础。还是直接上手撸源码啊?

    作者回复: 可以看看操作系统相关的部分,比如进程,虚拟地址,用户空间,内核空间。

    2019-06-24
  • nightmare
    第一selectorUpdate可以统一抽象封装注册的io事件,坐到面相抽象编程,将来如果nio的api接口有变动,也不需要改动ManangerSelector的代码 只需要新建一下selectorUpdate的子类来实现变更 第二 作为缓冲,,如果高并发的话,一下子很多channel注册到linux的epoll模型上,红黑树的层级就会很大,select的时候就比较耗时,有一个缓冲,可以均匀注册到linux的epoll模型上,既不会快速让红黑树发生过多旋转,也不会过多占用太多文件描述符
    2019-06-22
    18
  • -W.LI-
    李老师好!越看越不明白了,我承认我基础差也没去看源码。不晓得别的同学看不看的懂。 老师能不能画点图方便理解。managedSelector怎么工作的还是不清楚。 只看懂了四种生成消费模型 1.单线程 2.io多路复用,把selector当做生成着,处理的业务逻辑单做消费者。(缺点就是不能用缓存,接受任务和处理任务不在一个线程) 3.就是接受和处理放一个线程,整体放在多线程里面跑。好处就是可以用缓存 4.对3和2的优化。空余线程多的时候用3,不足用2。把接受到的任务缓存起来防止拒绝太多任务。 后面的就看不懂了。 问题1.managedSelector只负责监听read事件么?。accept事件他监听么?一个端口能被多个serversocketchannel监听么? 问题2.文中说managedselector由endpoint管理,具体怎么管理的呢?managedselector有多少个?怎么创建的?不同的managedselector之间channel共享么(个人感觉不共享会有并发冲突)? 问题3.线程不够用的时候具体怎么切换的呢?慢慢的全部都转成2还是保留一部分线程执行3一部分执行2。 问题4.我连channel是怎么注册到managedselector上的都没看明白。更别说销毁了。 其实就是想知道这几个类的生命周期,然候就是直接执行交互图,纯文字我想不出。希望老师解惑不胜感激
    2019-06-24
    2
    13
  • 掐你小77
    本章节中主要涉及的是三个组件:ManagerSelector,Producer(SelectoeProducer),ExecutionStrategy 其中三个组件的作用如下: 1,ManagerSelector用于其他调用者注册感兴趣事件和事件对应的处理逻辑。 如何注册感兴趣事件和处理逻辑呢?提供了两个接口:SelectorUpdate和Selectable, 其中SelectorUpdate接口让调用者可以网ManagerSelector中的Selector注册感兴趣事件, 其中Selectable接口让调用者提供一个处理逻辑。 2,Producer是一个发动机,它的作用就是:注册 --> 获取就绪的IO --> 产生对应的处理任务 上述对应了其内部的三个方法:processUpdates,select,processSelected 其对应的实现类为:ManagerSelector.SelectoeProducer 3,ExecutionStrategy则是上面Producer接口产生处理任务的执行策略了,jetty中默认的策略有: a,ProduceConsume 单线程创建和执行所有任务 b,ProduceExecuteConsume 专门线程创建任务,然后任务放进线程池中执行 c,ExecuteProduceConsume 一个线程中创建任务并执行,同时启动另一个线程进行任务的创建 和执行,其中顺序是:创建任务 --> 开启新线程(使用线程池) --> 执行任务 d,EatWhatYouKill 判断当前当前是否系统忙碌,在ProduceExecuteConsume和ExecuteProduceConsume 运行模式中切换 较于理解(不准确)的执行顺序:调用者 --> ManagerSelector --> SelectoeProducer --> ExecutionStrategy 这样总结对么?老师。
    2019-10-13
    3
  • sionsion
    https://webtide.com/eat-what-you-kill/ , 这里有一篇文章也做了解释,有兴趣的可以看看。
    2019-07-14
    2
  • Gary
    老师好,在 ManagedSelector # SelectorProductor # processUpdates() 源码中看到有这么一段 synchronized(ManagedSelector.this) { Deque<SelectorUpdate> updates = _updates; _updates = _updateable; _updateable = updates; } ··· for (SelectorUpdate update : _updateable){ ··· } 这里不直接循环 _updates 的原因是防止不断有请求填充了 _updates,导致前面的请求无法返回吗,感觉这里的_updateable 用一个局部变量就可以了,为啥他要作为类的字段
    2019-10-09
    2
    1
收起评论
显示
设置
留言
19
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部