`
sasion
  • 浏览: 34560 次
  • 性别: Icon_minigender_1
  • 来自: 广州
文章分类
社区版块
存档分类
最新评论

高性能的HTTP引擎—— Grizzly(二) Grizzly简介

阅读更多
                             Grizzly简介
正如前文所说,用Java技术来编写一个扩展性能很高的服务器软件是件很困难的事情。Java虚拟机的线程管理机制使得纯Java写的HTTP引擎很难响应成千上万的并发用户。正如Tomcat一样,在并发用户数不是很高的情况下能够获得很高的吞吐量,但是在高并发的情况下性能下降很快,变得不太稳定。

在JDK 1.4推出NIO之后,有很多基于NIO的框架出现,利用NIO的新特性,来编写高性能的HTTP引擎。其中以Jean-Francois Arcand的Grizzly最为引人瞩目。Grizzly最早被用于Sun Java System Application Server, Platform Edition 8.1。随后成为开源软件GlassFish的一部分。在今后,Sun Java System Application Server 9.x的Platform Edition和Enterprise Edition都会使用Grizzly作为HTTP引擎。

17.2.1  Grizzly的基本架构

图17-1描述了Grizzly的基本架构。



图17-1  Grizzly的基本架构

Grizzly的基本架构主要包含以下几个方面:Pipeline、SelectorThread和Task。下面分别加以介绍。

1. Pipeline
在com.sun.enterprise.web.connector.grizzly包下,有许多与Pipeline相关的类,例如Pipeline、KeepAlivePipeline、ThreadPoolExecutorPipeline、LinkedListPipeline等。Pipeline是个不太好理解的词汇,其实把这些类叫做ThreadPoolWrapper可能更加合适和容易理解。只要熟悉服务器端的软件,对Thread Pool(线程池)一定不会陌生。线程比起进程来说,消耗的资源要少,共享数据更加简单。因此,现在大多数服务器软件(特别是HTTP服务器)都会采用多线程模式。但是线程的创建和关闭仍然是比较慢的系统服务,聪明的服务器软件设计者会在系统启动的时候,预先创建一些线程,并且将这些线程管理起来,在系统正常运行的时候服务于客户的请求。通过这样的手段,线程不需要在使用的时候临时创建,大大提高了软件的运行速度和效率。对这种线程的管理方法叫做线程池。线程池中的线程需要互相协作,有序地执行客户的请求。一般用于同步线程的结构叫任务队列。客户的请求根据先后顺序被放到了任务队列中,线程池中空闲的线程会从任务队列中获得任务并执行。

Grizzly中的Pipeline实际上封装了一个Thread Pool(线程池)和一个任务队列。Pipeline的主要目的是封装了一个统一的接口,可以让Grizzly根据配置文件任意选择不同算法的线程池,来获得不同的特点和性能。在Grizzly中已经实现了好几种线程池。其中有ThreadPoolExecutorPipeline(基于java.util.concurrent.ThreadPoolExecutor来实现的线程池),还有LinkedListPipeline(使用简单的linklist数据结构管理的线程池)。在早期的Grizzly中还会看到一些其他的实现。经过测试以后,淘汰了一些性能不好的算法,目前只剩下这两种Pipeline了。事实上在大并发用户的测试中,LinkedListPipeline的性能是最好的,因此被设置为默认的选择。在以后的版本中,ThreadPoolExecutorPipeline也可能会消失,只保留性能最好的算法是明智的选择。但是现在还存在两种算法,其主要原因是java.util.concurrent.ThreadPoolExecutor的名声太响,所有的文章和测试都曾经证明过它的高性能。就连Grizzly的作者本身都不相信LinkedListPipeline的性能要比ThreadPoolExecutorPipeline好,只不过当前的测试结果事实如此。因此该作者自己也说,一旦有证据证明ThreadPoolExecutorPipeline的性能又重新超过LinkedListPipeline,他会立即将默认的设置指向ThreadPoolExecutorPipeline。

KeepAlivePipeline是一个特例,它并不是用来执行特定任务的,而是用来维护HTTP协议中的持久连接的状态,例如维护最大的持久连接数,持久连接的timeout时间等。另外,异步的socketChannel中缺少一个类似socket.setSoTimeout的函数,这个函数在保证服务器软件的可靠性和安全性(抗DOS攻击)上,具有重要的作用。Grizzly是用KeepAlivePipeline类来模拟socket.setSoTimeout的作用。

2. SelectorThread
这是Grizzly的主要入口类,位于com.sun.enterprise.web.connector.grizzly的包下。在SelectorThread中,SocketChannel和Selector被创建并被初始化。当网络有请求进来的时候,Selector会根据不同的请求类型和NIO的不同事件进行不同的处理。

当NIO的事件为OP_READ的时候,表明是原有的连接中有新的请求数据传过来了。这类请求属于ReadTask,应该交给负责处理ReadTask的处理器来处理。ReadTask有自己的Pipeline(也就是线程池)来处理,这样就不会占用主线程来处理Read的请求。

当NIO的事件为OP_ACCEPT的时候,表明是有新的请求进来了,这类请求属于AcceptTask,应该交给负责处理AcceptTask的处理器来处理。在老版本的GlassFish中,AcceptTask也有自己的Pipeline来处理,这样就让AcceptTask在主线程以外的线程中执行。但是经过多次性能测试和比较,发现当AcceptTask在主线程(SelectorThread)中执行的时候,性能最好。因此,在读最新的Grizzly源代码的时候,会发现图17-1中的AcceptPipeline根本不存在,因为AcceptTask已经由SelectThread类中HandleAccept函数来执行了。

当ReadTask执行完以后,表明整个请求的数据已经完全接收到,就可以进行请求处理了,请求处理属于ProcessTask,交给负责处理ProcessTask的处理器来处理。ProcessTask有自己的Pipeline(也就是线程池)来处理,这样就不会占用主线程来处理请求。

3. Task
在Grizzly的框架中包含下面几种任务。

(1)   AcceptTask:用于响应新的连接请求。前面已经说过,这个任务的类事实上已经不存在,没有单独抽象出来。因为处理Accept已经成为SelectThread内部的一部分了。

(2)   ProcessTask:用于处理并且响应请求。这个任务通常是对请求的数据进行解析,解析完后再将请求传递给其他服务的容器(如Servlet容器)进行真正的业务处理。

(3)   ReadTask:用于SocketChannel最初的读取操作。由于NIO是非阻塞的操作,最初的读取往往不能获得全部的请求数据,这时候,ReakTask会将任务委托给StreamAlgorithm,根据不同实现,用不同的方法将剩下的请求数据获取。

在com.sun.enterprise.web.connector.grizzly.algorithms的包下,Grizzly默认实现了4个算法:

l   ContentLengthAlgorithm

l   SeekHeaderAlgorithm

l   StateMachineAlgorithm

l   NoParsingAlgorithm

前3个算法主要是围绕HTTP请求中的Content-length字段来进行解析。只要能读到这个字段的值,那么我们就可以预先判断整个请求的长度,从而确定什么时候完成请求读取,接着进行请求处理了。第4个算法是对请求数据根本不进行预处理,假设所有的数据都读进来了。如果最后发现请求数据读得不完全,再交给请求处理任务(ProcessTask)来负责将剩下的内容读取过来。

17.2.2  源码阅读指南

根据图17-1的结构,结合Grizzly的源代码,可以看到Grizzly的大致脉络。

SelectorThread是个入口,根据Grizzly所在的不同环境,启动的方法有所不同。如果Grizzly作为单独可运行的应用(Grizzly可以从GlassFish中独立出来),在com.sun.enterprise. web.connector.grizzly.standalone包下的Main类是这样使用SelectorThread的:

【例17.4】单独运行的Grizzly对SelectorThread的调用:

private static void start(String args[]) throws Exception {

...

    SelectorThread selectorThread = null;

    String selectorThreadClassname = System.getProperty(SELECTOR_THREAD);

    if (selectorThreadClassname != null){

        selectorThread = loadInstance(selectorThreadClassname);

    } else {

        selectorThread = new SelectorThread();

    }

    selectorThread.setPort(port);

    StaticResourcesAdapter adapter = new StaticResourcesAdapter();

    adapter.setRootFolder(folder);      

    selectorThread.setAdapter(adapter);

    selectorThread.setDisplayConfiguration(true);

    selectorThread.initEndpoint();

    selectorThread.startEndpoint();

}

如果Grizzly是在GlassFish中,它作为服务线程,run()方法是整个线程启动的钥匙。从源码中很容易看出在run()方法中调用了startEndpoint()方法,startEndpoint()在做好一些准备工作之后,调用了startListener()。startListener()便进入了主线程的循环之中。在循环中只有一个方法,那就是doSelect()方法。

在doSelect()中,可以很清楚地看到NIO的框架结构。

【例17.5】SelectorThread中的doSelect():

selectorState = selector.select(selectorTimeout);

...

readyKeys = selector.selectedKeys();

iterator = readyKeys.iterator();

while (iterator.hasNext()) {

    key = iterator.next();

    iterator.remove();

    if (key.isValid()) {

        handleConnection(key);

    } else {

        cancelKey(key);

    }

}

与大多数NIO的架构一样,先是调用selector.select(selectorTimeout),看看当前的频道有没有数据准备好了。如果有的话,通过selector.selectedKeys()将准备好的这些频道的SelectionKey取到。对这些频道的处理就交给handleConnection(key)函数了。

【例17.6】SelectorThread中的handleConnection:

protected void handleConnection(SelectionKey key) throws

IOException,InterruptedException

{

    Task task = null;

    if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT){

        handleAccept(key);

        return;

    } else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ){

        task = handleRead(key);

    }

if (((SocketChannel)key.channel()).isOpen()) {

        task.execute();

} else {

        cancelKey(key);

}

}

handleConnection函数很短,但是有一些重要的特点需要指出来。handleConnection的主要功能是区分那些已经准备好的频道,看看它们是属于新的连接(OP_ACCEPT)还是有新的请求数据(OP_READ)。

如果是OP_ACCEPT,那么就调用函数handleAccept(key)。这个函数会在当前的线程内执行,主要的功能就是根据新来的连接创建新的频道,再将这个频道注册到Selector中。如果是OP_READ,那么就调用函数handleRead(key)。这个函数返回了一个Task。通过task.execute()将这个任务的实际运行交给Pipeline中的线程池来执行。换句话说,对新的请求数据的处理是在另外的线程中来处理的,而不是当前的线程。

事实上,在早期的Grizzly的版本中,对OP_ACCEPT的处理与OP_READ一样,也是有单独的任务(AcceptTask)和单独的线程来执行。但是经过性能测试,证明当对OP_ACCEPT的处理在主线程的时候性能最好。因此就取消了AcceptTask在单独线程中的处理,演化为当前的模型。

再随后的工作主要就交给ReadTask和ProcessTask去做了。这里不作详细的介绍。
分享到:
评论
1 楼 cnliuxj 2008-04-30  
很好,受益匪浅

相关推荐

    grizzly-utils-1.9.18-q.zip

    而“animation4j”这个名字暗示了它可能基于Java语言,与Grizzly框架有关,Grizzly是一个轻量级的Java服务器端框架,常用于构建高性能的网络应用。 在“animation4j”中,我们可以预见到它会提供一套完整的动画管理...

    grizzly-portunif-2.3-rc3.zip

    Grizzly通常指的是一个高性能、轻量级的Java网络应用框架,而PortUnif可能是它的一个组件,可能涉及到网络端口统一或者代理相关的功能。然而,描述中并未直接提到Grizzly或PortUnif,而是提到了“bencode.zip”和...

    fish-letter:鱼书——一款开源通信框架

    1. **高性能**:通过优化的I/O模型和高效的内存管理,鱼书能够在高并发环境下保持良好的性能,降低延迟,提高吞吐量。 2. **易用性**:框架的API设计简洁明了,使得开发者能够快速上手,减少学习成本。同时,丰富的...

    openstack quantum安装文档

    这个标签指出了文档的主题——OpenStack Quantum,即 OpenStack 中用于管理网络资源的组件。 #### 正文内容概览及详细解析 **1. 概述** - **什么是 OpenStack Networking?** - OpenStack Networking(现更名为 ...

    养老院管理系统:SpringBoot与Vue前后端不分离架构的设计与实现

    内容概要:本文详细介绍了基于SpringBoot和Vue开发的养老院管理系统的具体实现细节。该系统采用前后端不分离的架构,旨在快速迭代并满足中小项目的开发需求。文中涵盖了多个关键技术点,如数据库设计(组合唯一约束、触发器)、定时任务(@Scheduled、@Async)、前端数据绑定(Vue的条件渲染和动态class绑定)、权限控制(RBAC模型、自定义注解)以及报表导出(SXSSFWorkbook流式导出)。此外,还讨论了开发过程中遇到的一些常见问题及其解决方案,如CSRF防护、静态资源配置、表单提交冲突等。 适合人群:具备一定Java和前端开发经验的研发人员,尤其是对SpringBoot和Vue有一定了解的开发者。 使用场景及目标:适用于需要快速开发中小型管理系统的团队,帮助他们理解如何利用SpringBoot和Vue进行全栈开发,掌握前后端不分离架构的优势和注意事项。 其他说明:文章不仅提供了详细的代码示例和技术要点,还分享了许多实用的小技巧和避坑指南,有助于提高开发效率和系统稳定性。

    家族企业如何应对人才流失问题?.doc

    家族企业如何应对人才流失问题?

    员工关怀制度.doc

    员工关怀制度.doc

    路径规划领域中基于排序搜索的蚁群算法优化及其应用

    内容概要:本文详细探讨了对传统蚁群算法进行改进的方法,特别是在路径规划领域的应用。主要改进措施包括:采用排序搜索机制,即在每轮迭代后对所有路径按长度排序并只强化前20%的优质路径;调整信息素更新规则,如引入动态蒸发系数和分级强化策略;优化路径选择策略,增加排序权重因子;以及实现动态地图调整,使算法能够快速适应环境变化。实验结果显示,改进后的算法在收敛速度上有显著提升,在复杂地形中的表现更加稳健。 适合人群:从事路径规划研究的技术人员、算法工程师、科研工作者。 使用场景及目标:适用于需要高效路径规划的应用场景,如物流配送、机器人导航、自动驾驶等领域。目标是提高路径规划的效率和准确性,减少不必要的迂回路径,确保在动态环境中快速响应变化。 其他说明:改进后的蚁群算法不仅提高了收敛速度,还增强了对复杂环境的适应能力。建议在实际应用中结合可视化工具进行调参,以便更好地观察和优化蚂蚁的探索轨迹。此外,还需注意避免过度依赖排序机制而导致的过拟合问题。

    基于PSO算法的配电网分布式光伏选址定容优化及其Matlab实现

    内容概要:本文详细介绍了利用粒子群优化(PSO)算法解决配电网中分布式光伏系统的选址与定容问题的方法。首先阐述了问题背景,即在复杂的配电网环境中选择合适的光伏安装位置和确定合理的装机容量,以降低网损、减小电压偏差并提高光伏消纳效率。接着展示了具体的PSO算法实现流程,包括粒子初始化、适应度函数构建、粒子位置更新规则以及越界处理机制等关键技术细节。文中还讨论了目标函数的设计思路,将多个相互制约的目标如网损、电压偏差和光伏消纳通过加权方式整合为单一评价标准。此外,作者分享了一些实践经验,例如采用前推回代法进行快速潮流计算,针对特定应用场景调整权重系数,以及引入随机波动模型模拟光伏出力特性。最终实验结果显示,经过优化后的方案能够显著提升系统的整体性能。 适用人群:从事电力系统规划与设计的专业人士,尤其是那些需要处理分布式能源集成问题的研究人员和技术人员。 使用场景及目标:适用于希望深入了解如何运用智能优化算法解决实际工程难题的人士;旨在帮助读者掌握PSO算法的具体应用方法,从而更好地应对配电网中分布式光伏系统的选址定容挑战。 其他说明:文中提供了完整的Matlab源代码片段,便于读者理解和复现研究结果;同时也提到了一些潜在改进方向,鼓励进一步探索和创新。

    Prius2004永磁同步电机设计:从Excel到MotorCAD的全流程解析与实战技巧

    内容概要:本文详细介绍了丰田Prius2004永磁同步电机的设计流程,涵盖从初始参数计算到最终温升仿真的各个环节。首先利用Excel进行基本参数计算,如铁芯叠厚、定子外径等,确保设计符合预期性能。接着使用Maxwell进行参数化仿真,通过Python脚本自动化调整磁钢尺寸和其他关键参数,优化电机性能并减少齿槽转矩。随后借助橡树岭实验室提供的实测数据验证仿真结果,确保模型准确性。最后采用MotorCAD进行温升仿真,优化冷却系统设计,确保电机运行安全可靠。文中还分享了许多实用技巧,如如何正确设置材料参数、避免常见的仿真错误等。 适合人群:从事电机设计的专业工程师和技术人员,尤其是对永磁同步电机设计感兴趣的读者。 使用场景及目标:适用于希望深入了解永磁同步电机设计全过程的技术人员,帮助他们在实际工作中提高设计效率和精度,解决常见问题,优化设计方案。 其他说明:文章提供了丰富的实战经验和具体的操作步骤,强调了理论与实践相结合的重要性。同时提醒读者注意一些容易忽视的细节,如材料参数的选择和仿真模型的准确性。

    基于DSP28335的单相逆变器设计方案与实现:涵盖ADC采样、PWM控制、锁相环及保护机制

    内容概要:本文详细介绍了基于DSP28335的单相逆变器的设计与实现,涵盖了多个关键技术模块。首先,ADC采样模块用于获取输入电压和电流的数据,确保后续控制的准确性。接着,PWM控制模块负责生成精确的脉宽调制信号,控制逆变器的工作状态。液晶显示模块则用于实时展示电压、电流等重要参数。单相锁相环电路实现了电网电压的频率和相位同步,确保逆变器输出的稳定性。最后,电路保护程序提供了过流保护等功能,保障系统的安全性。每个模块都有详细的代码示例和技术要点解析。 适合人群:具备一定嵌入式系统和电力电子基础知识的研发人员,尤其是对DSP28335感兴趣的工程师。 使用场景及目标:适用于单相逆变器项目的开发,帮助开发者理解和掌握各个模块的具体实现方法,提高系统的可靠性和性能。 其他说明:文中不仅提供了具体的代码实现,还分享了许多调试经验和常见问题的解决方案,有助于读者更好地理解和应用相关技术。

    SecureCRT安装包

    SecureCRT安装包

    C# WPF MVVM架构下的大屏看板3D可视化开发指南

    内容概要:本文详细介绍了如何利用C#、WPF和MVVM模式构建一个大屏看板3D可视化系统。主要内容涵盖WPF编程设计、自定义工业控件、数据库设计、MVVM架构应用以及典型的三层架构设计。文中不仅提供了具体的代码实例,还讨论了数据库连接配置、3D模型绑定、依赖属性注册等关键技术细节。此外,文章强调了项目开发过程中需要注意的问题,如3D坐标系换算、MVVM中命令传递、数据库连接字符串加密等。 适合人群:具备一定C#编程基础,对WPF和MVVM模式有一定了解的研发人员。 使用场景及目标:适用于希望深入了解WPF和MVVM模式在实际项目中应用的开发者,特别是那些从事工业控制系统、数据可视化平台开发的专业人士。通过学习本文,读者可以掌握如何构建高效、稳定的大屏看板3D可视化系统。 其他说明:本文提供的设计方案和技术实现方式,可以帮助开发者更好地理解和应用WPF和MVVM模式,同时也能为相关领域的项目开发提供有价值的参考。

    基于java SSM 框架的酒店管理系统.zip

    基于ssm的系统设计,包含sql文件(Spring+SpringMVC+MyBatis)

    非厄米超表面双参数传感器的COMSOL建模与应用

    内容概要:本文详细介绍了利用COMSOL进行非厄米超表面双参数传感器的设计与实现。首先,通过构建超表面单元并引入虚部折射率,实现了PT对称系统的增益-损耗交替分布。接着,通过频域扫描和参数化扫描,捕捉到了复频率空间中的能级劈裂现象,并找到了奇异点(Exceptional Point),从而显著提高了传感器对微小扰动的敏感度。此外,文章探讨了双参数检测的独特优势,如解耦温度和折射率变化的能力,并展示了其在病毒检测、工业流程监控等领域的潜在应用。 适合人群:从事光学传感器研究的专业人士,尤其是对非厄米系统和COMSOL仿真感兴趣的科研人员。 使用场景及目标:适用于需要高精度、多参数检测的应用场合,如生物医学检测、环境监测等。目标是提高传感器的灵敏度和分辨率,解决传统传感器中存在的参数交叉敏感问题。 其他说明:文中提供了详细的建模步骤和代码片段,帮助读者理解和重现实验结果。同时,强调了在建模过程中需要注意的关键技术和常见问题,如网格划分、参数设置等。

    怎样健全员工福利体系.docx

    怎样健全员工福利体系.docx

    离职证明范本.doc

    离职证明范本.doc

    6538b79724855900a9c930904a302920.part6

    6538b79724855900a9c930904a302920.part6

    员工离职单.doc

    员工离职单.doc

    COMSOL中超材料异常折射仿真的关键技术与实现

    内容概要:本文详细介绍了在COMSOL中进行超材料异常折射仿真的关键技术。首先解释了异常折射现象及其产生的原因,接着通过具体代码展示了如何利用相位梯度和结构色散精确计算折射角。文中还讨论了边界条件的设置、网格划分的优化以及参数化扫描的应用。此外,提供了多个实用脚本和技巧,帮助提高仿真的精度和效率。最后强调了验证结果的重要性和一些常见的注意事项。 适合人群:从事电磁仿真研究的专业人士,尤其是对超材料和异常折射感兴趣的科研人员和技术开发者。 使用场景及目标:适用于需要深入理解和解决超材料中异常折射问题的研究项目。主要目标是掌握COMSOL中异常折射仿真的完整流程,确保仿真结果的准确性并优化计算性能。 其他说明:文章不仅提供了详细的代码示例和技术细节,还分享了许多实践经验,有助于读者更好地应对实际仿真过程中可能出现的问题。

Global site tag (gtag.js) - Google Analytics