浅析 Jetty 中的线程优化思路

news2024/9/30 22:09:32

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

本文介绍了 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。

二、Jetty 中的 Selector 交互

2.1 传统的 Selector 实现

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

具体过程是:

  1. 启动一个线程;

  2. 在一个死循环里不断地调用 select 方法,检测 Channel 的 I/O 状态;

  3. 一旦 I/O 事件到达,就把该 I/O 事件以及一些数据包装成一个 Runnable;

  4. 将 Runnable 放到新线程中去处理。

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

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

这样设计的好处:

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

这样设计的缺陷:

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

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

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

2.2 Jetty 中的 ManagedSelector 实现

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

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

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

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

public class ManagedSelector extends ContainerLifeCycle implements Dumpable
{
    // 原子变量,表明当前的ManagedSelector是否已经启动
    private final AtomicBoolean _started = new AtomicBoolean(false);
     
    // 表明是否阻塞在select调用上
    private boolean _selecting = false;
     
    // 管理器的引用,SelectorManager管理若干ManagedSelector的生命周期
    private final SelectorManager _selectorManager;
     
    // ManagedSelector的id
    private final int _id;
     
    // 关键的执行策略,生产者和消费者是否在同一个线程处理由它决定
    private final ExecutionStrategy _strategy;
     
    // Java原生的Selector
    private Selector _selector;
     
    // "Selector更新任务"队列
    private Deque<SelectorUpdate> _updates = new ArrayDeque<>();
    private Deque<SelectorUpdate> _updateable = new ArrayDeque<>();
     
    ...
}

2.2.1 SelectorUpdate 接口

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

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

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

/**
 * A selector update to be done when the selector has been woken.
 */
public interface SelectorUpdate
{
    void update(Selector selector);
}

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

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

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

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

_selector.submit(_updateKeyAction);

这个 _updateKeyAction 就是一个 SelectorUpdate 实例,它的 update 方法实现如下:

private final ManagedSelector.SelectorUpdate _updateKeyAction = new ManagedSelector.SelectorUpdate()
{
    @Override
    public void update(Selector selector)
{
        // 这里的updateKey其实就是调用了SelectionKey.interestOps(OP_READ);
        updateKey();
    }
};

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

2.2.2 Selectable 接口

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

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

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

public interface Selectable
{
    // 当某一个Channel的I/O事件就绪后,ManagedSelector会调用的回调函数
    Runnable onSelected();
 
    // 当所有事件处理完了之后ManagedSelector会调的回调函数
    void updateKey();
}

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

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

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

三、Jetty 的线程优化思路

3.1 Jetty 中的 ExecutionStrategy 实现

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

  1. 如何注册 Channel 以及 I/O 事件

  2. 提供什么样的处理类来处理 I/O 事件

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

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

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

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

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

private class SelectorProducer implements ExecutionStrategy.Producer
{
    private Set<SelectionKey> _keys = Collections.emptySet();
    private Iterator<SelectionKey> _cursor = Collections.emptyIterator();
 
    @Override
    public Runnable produce()
{
        while (true)
        {
            // 如果Channel集合中有I/O事件就绪,调用前面提到的Selectable接口获取Runnable,直接返回给ExecutionStrategy去处理
            Runnable task = processSelected();
            if (task != null)
                return task;
             
           // 如果没有I/O事件就绪,就干点杂活,看看有没有客户提交了更新Selector的任务,就是上面提到的SelectorUpdate任务类。
            processUpdates();
            updateKeys();
 
           // 继续执行select方法,侦测I/O就绪事件
            if (!select())
                return null;
        }
    }
 }

SelectorProducer 是 ManagedSelector 的内部类。

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

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

  1. 如果 Channel 集合中有 I/O 事件就绪,调用前面提到的 Selectable 接口获取 Runnable,直接返回给 ExecutionStrategy 处理。

  2. 如果没有 I/O 事件就绪,就干点杂活,看看有没有客户提交了更新 Selector 上事件注册的任务,也就是上面提到的 SelectorUpdate 任务类。

  3. 干完杂活继续执行 select 方法,侦测 I/O 就绪事件。

3.2 Jetty 的线程执行策略

3.2.1 ProduceConsume(PC) 线程执行策略

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

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

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

3.2.2 ProduceExecuteConsume(PEC) 线程执行策略

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

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

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

3.2.3 ExecuteProduceConsume(EPC) 线程执行策略

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

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

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

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

当系统比较忙线程不够时,切换成 ProduceExecuteConsume 策略。

这么做的原因是:

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

于是 Jetty 做了一个优化

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

四、总结

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

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

  1. 在保证不发生线程饥饿的情况下,尽量使用同一个线程生产和消费可以充分利用 CPU 缓存,并减少线程切换的开销。

  2. 根据实际场景选择最适合的执行策略,通过组合多个子策略也可以扬长避短达到1+1>2的效果。

参考文档:

  1. Class EatWhatYouKill

  2. Eat What You Kill

  3. Thread Starvation with Eat What You Kill

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/686932.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Origin如何绘制带拟合曲线的频数分布图?

文章目录 0.引言1.准备数据2.频数分布参数设置并绘图3.拟合曲线参数设置并绘图 0.引言 笔者研究的方向关于点云数据采集和建模算法&#xff0c;在对一个平面进行数据采集并建模后&#xff0c;需要实验结果进行精度分析&#xff0c;为了知道建模结果的点云集中于平面的程度&…

专场来袭,深度解读阿里云视频云的全智能进化

云智深度融合&#xff0c;视频云将幻化出怎样的新光景&#xff1f; 01 「云智新生」_ 视频云的全智能进化 新数智时代&#xff0c;云和AI在走向深度融合&#xff0c;以云计算为基石、以AI为引擎的云智深度融合&#xff0c;俨然成为行业共识。以“云智”为支撑&#xff0c;视频云…

基于JavaWeb的土特产销售购物商城的设计与实现

1.引言 随着互联网技术的不断发展&#xff0c;电子商务已成为一种重要的经济活动形式。土特产作为传统文化的代表之一&#xff0c;在旅游业中具有非常重要的地位。因此&#xff0c;将土特产销售与电子商务相结合&#xff0c;建立一个基于JavaWeb的土特产销售购物商城&#xff…

java进阶—代码演示多线程的生命周期

前言 回顾一下 到现在&#xff0c;我们已经知道了 线程的三种创建方式及其选择线程的常用方法线程的安全问题以及线程的死锁线程之间的通信&#xff08;等待唤醒&#xff09; 今天&#xff0c;我们一起来看看线程的生命周期&#xff0c;生命周期这个词在后续的javaweb 相关知…

【论文系列解读】LLM构建通用视觉(SUR-Adapter)声音模型(Tango)

通用视觉&音频模型的思考 0. 视觉(Diff)怎么和多模态结合(0) 总结(1) 关键技术(1-1) LangChain(1-2) **Versatile Diffusion**(1-3) Tango(1-4) SUR-adapter 1. SUR-Adapter(0) 总结(1) 摘要(2) 相关工作(2-1) 文图生成 & LLM(2-2) 语义理解和推理数据集(2-2-1) 数据收…

【案例教程】环境多介质逸度模型实践技术与典型案例【代码】应用

【原文链接】&#xff1a;【案例教程】环境多介质逸度模型实践技术与典型案例【代码】应用https://mp.weixin.qq.com/s/i8BpApcz1p4Ua6bytxhGwA 内容简要&#xff1a;【注&#xff1a;提供以下模型所有教程】 专题一&#xff1a;基本理论 1.逸度的定义 2.逸度模型的基本原理…

蓝牙音箱中应用的国产蓝牙芯片

蓝牙音箱指的是内置蓝牙芯片&#xff0c;以蓝牙连接取代传统线材连接的音响设备&#xff0c;通过与手机平板电脑和笔记本等蓝牙播放设备连接&#xff0c;达到方便快捷的目的。蓝牙音箱以便携音箱为主&#xff0c;外形一般较为小巧便携&#xff0c;蓝牙音箱技术也凭借其方便人的…

AI绘画中的负面词 是如何正确使用以及发挥作用

Stable Diffusion的艺术或图像生成&#xff0c;有些参数是必不可少的&#xff0c;其中之一就是负提示。本次将深入解析稳定扩散中的负提示是什么&#xff0c;如何使用这个参数来生成高质量的图像。 文章目录 negative prompt如何正确使用&#xff1f;正面词控制通过负面词控制 …

IP地址、MAC地址、互联网、WLAN、运营商、子网掩码、网络地址、网段、网关、集线器、光纤、基站

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

从2PC和容错共识算法讨论zookeeper中的Create请求 | 京东云技术团队

最近在读《数据密集型应用系统设计》&#xff0c;其中谈到了zookeeper对容错共识算法的应用。这让我想到之前参考的zookeeper学习资料中&#xff0c;误将容错共识算法写成了2PC&#xff08;两阶段提交协议&#xff09;&#xff0c;所以准备以此文对共识算法和2PC做梳理和区分&a…

近日网上传出消息,ARM正与Intel共同开发制造芯片

近日网上传出消息&#xff0c;ARM正与Intel共同开发制造芯片&#xff0c;让芯片设计者能够基于Intel 18A制程打造低功耗的SoC。另有多位业内人士透露&#xff0c;这次制造的芯片将主要用于移动设备、笔记本电脑、数据中心等。 若这一消息属实&#xff0c;会对中国芯片行业带来…

常见的SQL优化方案

1. insert优化 1.1 批量插入 我们之前插入数据都是一条一条插入的&#xff0c;会导致频繁操作数据库&#xff0c;从而影响性能。比如遍历某个集合&#xff0c;然后逐个使用insert语句插入数据库表中 INSERT INTO table (name, age) VALUES (张三, 18); INSERT INTO table (na…

6-js基础-3

JavaScript 基础 - 3 知道什么是数组及其应用的场景&#xff0c;掌握数组声明及访问的语法&#xff0c;具备利用数组渲染柱形图表的能力 今日重点&#xff1a; 循环嵌套数组综合案例 今日单词&#xff1a; 循环嵌套 利用循环的知识来对比一个简单的天文知识&#xff0c;我们…

arcpy制作脚本工具相关(制作并添加脚本工具、脚本工具消息提示、arcpy工具自动获取数据)

前言 采用arcpy制作arcgis的脚本工具&#xff0c;记录如下。 一、脚本工具的制作 制作脚本工具分为两步&#xff1a; 1、先编写对应功能的python脚步 2、将对应功能的脚本修改为&#xff0c;能自己手动选择输入输出数据的脚本 3、将修改完的脚本添加到工具箱 1、编写代码 我…

618种草观察丨益生菌「蓝」在一起,酸奶「地域控」?

2023年的“618”&#xff0c;食饮行业中&#xff0c;种草战线格外热闹&#xff0c;品牌花式“开屏”&#xff0c;吸引消费者。 数说故事发布「SMI社媒心智品牌榜」&#xff0c;本期持续关注健康食品&#xff0c;也新增了今夏网红酸奶行业&#xff0c;一起看看这两大赛道中&…

使用power rail连接secondary pg pin的方法

过往文章&#xff1a; secondary pg pin的作用与连接 之前写到过secondary pg pin的种类与几种连接方式&#xff0c;其中使用最多的方法是NDR rule &#xff0b; route_group的方式&#xff0c;这样工作量少&#xff0c;不容易出错&#xff0c;但也有弊端&#xff0c;那就是一…

ctfshow web入门 内网渗透篇

web859 首先ssh连接上之后传个fscan上去&#xff0c;扫描下内网靶机 发现.5和.6的比较可疑。 一个存在web服务&#xff0c;一个存在445端口。 先看下445端口&#xff0c;靶机给我们提供了msf&#xff0c;所以直接用msf打下Samba msfconsole use exploit/linux/samba/is_kno…

数字贸易下转口贸易企业如何高效管理?

什么是转口贸易&#xff1f;是指企业在国内购买进口货物&#xff0c;经过加工、组装、包装等方式改变其性质&#xff0c;再出口到海外市场的贸易活动。这种贸易方式对于促进国际贸易和提高企业竞争力都有着非常重要的作用&#xff0c;但同时也存在着一些风险和难点&#xff0c;…

如何向领导建议数字化转型应着手数据治理?_光点科技

在当今数字化时代&#xff0c;企业的数字化转型已经成为一个迫切的任务。然而&#xff0c;数字化转型不仅仅涉及技术的引入&#xff0c;更需要在数据治理方面进行全面的改进。 一、数据治理是数字化转型的基石。 数据是企业最重要的资产之一&#xff0c;通过对数据的管理和利用…

电气工程师日常工作常遇到的41种接线方法(二)

021 缘耐压测试仪线路 这种绝缘耐压测试仪可测灯具&#xff0c;将待测灯具与A、B两接线柱接好&#xff0c;按下按钮SB1&#xff0c;中间继电器KA1得电并自锁&#xff1b;然后将调压器VT(1∶10&#xff0c;输出0~250V)调至需测的电压值&#xff0c;如需调到1500V则将VT调到电压…