`
wbj0110
  • 浏览: 1602416 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

Jetty 服务器架构分析

阅读更多

以 jetty7 作为分析目标, Jetty 是由一个或多个 connector 核心组件以及一些列 handler 组件和一个线程池组成,看一下结构图:

 

 

           Connector 负责监听接收客户连接请求,而 handler 组件则负责处理请求并给予响应,前面两个组件工作所需要的线程资源都直接从线程池 ThreadPool 中获取。 Jetty  Server 可以有多个connector 在不同的端口上监听客户请求,而每个 connector 根据具体的使用场景不同可以有不同的实现,例如采用非阻塞 NioConnector 、阻塞 SocketConnector 等等,而对于请求处理的 handler 组件,也根据具体需要可以使用不同的 handler ,此种设计提高了 jetty 的灵活性,需要 servlet ,则可以使用 servletHandler ,需要 session ,则再增加一个 sessionHandler ,也就是说我们完全可以不使用 servlet 或者 session ,只要不配置这个 handler 就行了。

    要启动和协调上诉核心组件工作, Jetty 提供了一个 Server 类来做这个事情 , 也就是说 Server 是应用的起始点,他负责创建并初始化 connector  handler  ThreadPool 组件,然后调用 start方法启动他们,让所有组件都处于待命状态,因此 Server 类是一个比较重要的 Façade, 值得注意的 Server 类本身也是一个 handler.

 

一、       组件生命周期

 

 

对于 jetty 来说,每个组件都有其生命周期, jetty 采用了统一的 LifeCyle 接口来控制,我们来看下,类图结构:

connector,handler 等组件全部都直接或间接实现了 LifeCyle 接口,刚才说了 Server 也是 Handler ,同时他也是启动或协调组件工作的类,也就是说 Server 可以通过 LifeCyle 接口控制其他组件的生命周期,通过 start 方法可以启动 server, 通过 stop 则关闭了 server 

 

 二、       Connector 组件

 

 

  Connnetor 在实现上有NIO 、BIO 两种实现方式,并且支持AJP 协议、和SSL 。

三、       Handler 组件

所有的 handler 组件都实现了 Handler 接口,可以看到, Handler 是可以以链表的形式相互组合的, Server 作为服务入口,本身也是 handler ,他继承了 HandlerWrapper 接口,我们看以看到他带了一个 handler 的引用变量,我们可以注入ServletHandler 支持 servlet, 注入 WebAppContext 则支持我们的 webapp 应用。

 

 

 四、       启动过程  

 

  先看下 jetty 的目录结构

 

 

看几个主要目录的含义,

Bin 目录定义了启动 jetty 需要的 sh 文件,主要用在 linux 中, windows 中可以直接 java start.jar 启动服务器,

  Contexts 目录主要放置跟应用相关的 context 配置文件,跟应用相关

etc 目录放置跟服务器相关的配置文件,其中会定义 contexts 目录所在的位置

lib 是服务器所需要的 jar 

webapps 是放置应用程序的位置,当然也可以通过在 contexts 中或者 etc 中自定义

 

我们从外部启动一个 jetty 服务器的过程:

首先从 Start.jar 开始,这个 jar 定义了解析命令行的 Main 这个类, Main 主要负责解析 start.ini 配置文件 ,start.ini 中定义了 JVM 需要的参数以及 etc 目录中用到的 xml 配置文件,如下图:

 

 

 

 

 

 

 

 

然后由 Config 类解析出 stat.ini  OPTIONS 选项指定的模块的包的位置用来加入到 classpath 中,这些模块的包都定义在 start.config 文件中 ( 该文件可以在 start.jar 包中找到 ) ,截取一个片段给大家看下:  

 

 

 

 

这个文件的配置是需要有一定的语法在里面的,有兴趣的可以研究一下,也就是说,通过在 start.ini 中定义 OPTIONS 以及在 start.config 中定义模块路径就可以确定把哪些 jar 加入到环境变量中。

         以上准备工作做完之后,就可以真正开始服务器的处理了,这时你有两种选择,第一种是在本进程中通过反射方式启动,但是缺点是 start.ini 中配置的 JVM 参数就形同虚设了,因为 java 进程已经起来了,不能再按照新的堆参数等重新设置了;第二种方式就是重新启动一个进程,就可以重新设置参数,前面说了, start.ini 中得到了启动参数, start.config 中有了 MainClass  Classpath需要的 jar 包,则可以直接用 java xxx 方式启动了,要使用这种方式启动,只需要在 start.ini 中配置 –exec 参数即可。

         MainClass 默认是 XmlConfigration 类,当然自定义的话,可以在 start.config 中去更改, XmlConfigration 做几件事情: 1 、根据 start.ini 中的定义的配置文件进行解析 , 例如 etc/jetty.xml  2、通过自己的 IOC 将这些服务组件组装在一起 3 、最后调用 start 方法启动这些组件。

说到XmlConfiguration ,XmlConfiguration 利用自己实现的 IOC 组装 Server 的全过程如下图所示:

这里可以看到 3 个关键的配置文件, jetty.xml 、 jetty-deploy.xml 、以及 contexts/xxx.xml

 

l  Jetty.xml 文件中定义了入口类 Server, 以及其所需要的线程池、 Connector  Handler 

l  Jetty-deploy.xml 中则定义了部署 web 应用用到部署工具,在其中指定了部署 web 应用的两种方式,类似于 tomcat, 如果采用 webappProvider ,则表示将 web 应用放在 webapp 下即可生效,如果采用 ContextProvider ,则需要定义 Contexts 目录所在位置,只要在该目录下放置任何应用的 context 配置文件就可以生效。

l  Xxx.xml 这是一个用户自定义文件,表示采用 ContextProvider 时,在其中定义一个 WebAppContext  handler, 它指定了我们应用所在的位置,便于加载。

 

XmlConfiguration 解析装配完毕之后,就开始启动服务, Jetty 的启动是从 Server 开始的,我们来看一下服务器真正的启动过程。

 

 

 

 

 

 

 

从上图中我们能大概看出服务器启动过程,先是由用户设置好需要的核心组件,然后调用 Server.start() 开始启动服务,其中会首先启动 handler 处理器,之后启动用户自定义组件,这个自定义组件需要实现 LifeCyle 接口,最后才启动 Connector 接受请求。可以想到,关闭过程恰好是反过来,首先关闭接受请求的 connector ,然后再关闭用户自定义组件,最后关闭 handler.

         我们再来详细看一下 Server 源代码的核心实现过程,当调用 start 方法时,其实是调用其祖先类 AbstractLifeCycle 中方法,该方法在这里有一个模板实现,如下:

 

  1. public final void start() throws Exception  
  2.    {  
  3.        synchronized (_lock)  
  4.        {  
  5.            try  
  6.            {  
  7.                if (_state == __STARTED || _state == __STARTING)  
  8.                    return;  
  9.                setStarting();  
  10.                doStart();  
  11.                setStarted();  
  12.            }  
  13.            catch (Exception e)  
  14.            {  
  15.                setFailed(e);  
  16.                throw e;  
  17.            }  
  18.            catch (Error e)  
  19.            {  
  20.                setFailed(e);  
  21.                throw e;  
  22.            }  
  23.        }  

 

 

 

 

 

 

  • Connector 启动过程

 

 

看下 Connector 的详细启动过程: (  NIO 为例 )

 

NIOConnector 启动过程中,先创建了多个 SelectSet 对象,每个 SelectSet 负责一个 NIO  Selector ,专门用于监听 read 事件 ( 这里利用的多线程 Reactor 模式, http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf ,当然这里仅仅是创建了对象,并没有启动,后面会提到。

SelectorManager 

 

 

 

然后再调用 open 创建了一个 blocking 的阻塞 channel ,专门用于接受用户的新连接,我们看下:

 

 

[c-sharp] view plaincopyprint?
 
  1. public void open() throws IOException  
  2.    {  
  3.        synchronized(this)  
  4.        {  
  5.            if (_acceptChannel == null)  
  6.            {  
  7.                // Create a new server socket  
  8.                _acceptChannel = ServerSocketChannel.open();  
  9.                // Set to blocking mode  
  10.                _acceptChannel.configureBlocking(true);  
  11.                // Bind the server socket to the local host and port  
  12.                _acceptChannel.socket().setReuseAddress(getReuseAddress());  
  13.                InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort());  
  14.          _acceptChannel.socket().bind(addr,getAcceptQueueSize());  
  15.                _localPort=_acceptChannel.socket().getLocalPort();  
  16.                if (_localPort<=0)  
  17.                    throw new IOException("Server channel not bound");  
  18.            }  
  19.        }  

 

 

随后从线程池中分配了 N  ( 可以在配置文件中配置 ) 线程用于启动 SelectSet 监听 read 事件。

 

 

 

 

 

 

 

 

 

 

[c-sharp] view plaincopyprint?
 
  1. synchronized (this)  
  2.         {  
  3.             _acceptorThread = new Thread[getAcceptors()];  
  4.             for (int i = 0; i < _acceptorThread.length; i++)  
  5.                 _threadPool.dispatch(new Acceptor(i));  
  6.             if (_threadPool.isLowOnThreads())  
  7.                 Log.warn("insufficient threads configured for {}",this);  
  8.         }  

 

最后再分配 1 个线程用于 accept 用户的新连接,新连接来之后,会将其设置为 nonblocking 模式,之后就将其 Register 给某个 SelectSet 去监听 read 事件,然后又返回来继续监听新连接:

 

 

[c-sharp] view plaincopyprint?
 
  1. _manager.dispatch(new Runnable()  
  2.         {  
  3.             public void run()  
  4.             {  
  5.                 final ServerSocketChannel server=_acceptChannel;  
  6.                 while (isRunning() && _acceptChannel==server && server.isOpen())  
  7.                 {  
  8.                     try  
  9.                     {  
  10.                         SocketChannel channel = server.accept();  
  11.                         channel.configureBlocking(false);  
  12.                         Socket socket = channel.socket();  
  13.                         configure(socket);  
  14.                         _manager.register(channel);  
  15.                     }  
  16.                     catch(IOException e)  
  17.                     {  
  18.                         Log.ignore(e);  
  19.                     }  
  20.                 }  
  21.             }  
  22.         });  

 

 

 

 

  • Handler 启动过程

 

 

Jetty 将所有的真正处理请求的动作都抽象成了 Handler ,因此做事情的组件都是实现了这个接口的,包括上图所示的 WebAppContext 等等,需要做什么样的工作,那么就添加什么样的 Handler ,这里 SessionHandler 不是必须的,但是默认是创建好的。 ServletHandler 主要负责处理 web 应用的 Servlet  Filter 等工作,最后将请求直接交给 Servlet  Filter 都是在这里完成。

 

   这里展示的 Handler 的启动过程其实是在准备 web 应用环境,例如解析 web 应用的 web.xml 等等工作,做好一切准备工作。

说过了服务器启动,最后来看一下请求处理过程, 服务器启动好后,处于待命状态,请求来了,请求处理过程由分两个建阶段:

 

 

  • 请求连接建立过程 (  NIO 为例 )

 

     前面有提到,从线程池中固定分配了一个线程专门用于等待新连接,就是上图的监听线程,没有请求来时,该线程是阻塞在 accept () 方法上的,当新连接来建立连接时, accept 方法分配了一个 socket ,并将其设置为nonblocking, 最后要做的就是将该 socket 丢给某个 Acceptor 线程 ( 基本上机会均等 ) 处理,然后立马返回继续处于接受状态,可以这个线程的工作是相当的简单的,效率那也是相当的高。

         Acceptor 线程有很多个 ( 全部来自于线程池,并且固定分配出来,基于 jetty.xml 配置中的 Acceptors 配置数量 ) ,每个线程都维护了一个 SelectSet, 每个 SelectSet 又对应了一个 Selector, 这些线程会检测当前是否有任务来,例如检测 changes 队列中是否有任务,有并且是新连接,那么就迅速建立一个 endpoint 点负责管理这个 socket ,并注册 read 事件,后续该 selector 就会负责该连接的 read 事件监听。

         对于连接很多的情况,这里分很多个 Selector 来分别监听,提高了效率。

 

 

 

 

  • 请求数据处理过程 (  NIO 为例 )

 

当数据发送过来时, Selector 检测到 read 事件,会立马调用 endpoint  schedule() 方法,该方法目的就是从线程池分配一个 worker 线程专门来处理这个 read 事件,而自己却立马返回继续监听,可见,这里也是一个高效的处理方式。

业务线程分配成功后,负责请求的读取以及解析,如果请求是完整的,那么就开始调用 Server  handle 方法 (server 本身就是一个 handler) ,开始 handler 的处理,最后调用到 SerlvetHandler,最终交给 Servlet  Filter ,开始了我们的自己应用。

 

 

      后记

 

 

 

1、  Jetty 的模块化做得非常好,可以随时替换其中的绝大部分关键部件,也可以拆掉,例如不需要处理 Session ,可以简单配置一下即可搞定,不需要处理 Servlet, 可以不用配置 ServletHandler.

2、  jetty 采用非阻塞 IO 时,我们可以看到从头到尾的几次线程池分配情况,第一次 分配一个固定线程监听新连接,第二次 分配 N 个固定线程监听 read 事件(这里的 N 个线程在 7.3 版本中配置文件中配置 acceptors 数量即可,也就是说会从线程池固定分配 N 个线程出来),第三次 分配线程就是 read 事件到来之后,立即分配一个业务线程 ( 这个是临时的,用了要回收 ) 处理数据直到我们应用返回结果。最后有一个地方 上面都没有说到,那就是超时等原因要关闭连接时,是分配了临时线程来处理这些事情

3、  模块化、切分 task

4、  小,真的很小

分享到:
评论
2 楼 秦时明月黑 2017-02-15  
   
1 楼 勇敢的核桃 2013-12-18  
这么好的文章,怎么没人顶呢?
写的真好,省得俺看源码了

相关推荐

    jetty 架构

    与许多其他Web服务器不同,Jetty没有采用多层架构,而是采用了基于事件驱动的单线程模型,这使得它在处理高并发请求时表现出色。每个连接都由一个单独的线程负责,避免了线程上下文切换带来的开销,提高了系统性能。...

    Jetty 核心架构

    Jetty采用了模块化的设计思路,使得各个组件可以灵活地组合在一起,从而构建出不同的服务架构。下面详细介绍Jetty中的主要组件及其装配过程。 ##### 1.2 组件装配 - **组件类型**:Jetty中的主要组件包括Server、...

    Jetty内嵌服务器实例

    内嵌Jetty意味着将Jetty服务器直接集成到你的Java应用中,而不是作为一个独立的服务来运行。这种方式提供了更高的灵活性和控制权,特别适合于快速迭代的开发环境或者需要自定义服务器配置的情况。 在“Jetty内嵌...

    jetty服务器

    - Jetty服务器的安装非常简单,通常只需将下载的压缩包(如`jetty-distribution-7.6.5.v20120716`)解压到任意目录即可。解压后的目录包含了所有运行所需文件,包括启动脚本和配置文件。 - 部署Web应用时,只需将...

    Jetty源码分析.pdf

    #### 二、Jetty架构分析 - **总括**:Jetty的核心设计理念在于其简洁高效的架构设计,使得开发者能够在短时间内轻松搭建起Web应用环境。 - **主要组成部分**:Jetty可以大致分为四个主要部分——`HttpServer`、`...

    jetty 适合jdk1.8用的服务器

    1. **Jetty服务器核心**:包含了运行Jetty服务器所需的基本组件。 2. **Servlet容器**:支持Servlet 3.1规范,可以处理HTTP请求并分发到对应的Servlet。 3. **配置文件**:如`start.ini`或`jetty.xml`,用于配置...

    应用服务器jetty8.0

    Jetty 8.0是Java应用服务器的一种,主要用于托管Web应用程序。它是一个开源项目,以其轻量级、高效和易于...用户可以通过遵循read-Me.text中的指南来设置和运行Jetty服务器,然后将他们的Web应用部署到这个服务器上。

    jetty-6.1.9服务器(2),包含源码

    Jetty服务器的架构设计使其可以轻松地与其他Java库集成,例如Spring框架,这使得开发者能够在不增加太多复杂性的情况下构建复杂的Web应用。它的模块化特性意味着你可以根据需要选择安装和配置特定的服务,如...

    Jetty

    为了方便管理和调试,Jetty提供了一些工具,如`jetty-maven-plugin`,它可以将Maven项目直接部署到Jetty服务器上。此外,还有`jetty-admin`和`jetty-console`等工具,用于远程管理和监控Jetty实例。 ### 《Jetty6_...

    jetty.jar,jetty-sslengine.jar,jetty-util.jar

    这个jar文件包括了Jetty服务器的基本架构,如服务器启动、线程池管理、HTTP连接器、请求处理等。此外,它还支持WebSocket、Continuation和 Comet编程模型,这些都是现代Web应用中的重要特性。 其次,jetty-...

    jetty html5 websocket服务器

    为了在Jetty服务器上部署WebSocket应用,可以将WebSocket端点类打包到一个Java Web应用程序中,并通过Jetty的Web应用部署机制(如`web.xml`配置或使用Jetty的`Server`和`WebAppContext`类)进行部署。 标签"jetty...

    Android平台i-Jetty服务器在智能家居中的应用研究.pdf

    i-Jetty服务器通过移植到ARM平台和Android系统中,利用其Web服务的功能,可以提供基于浏览器的统一监控界面。 在硬件层面,系统采用蓝牙技术与家居中的被控设备进行通信。蓝牙作为一种短距离无线通信技术,具有成本...

    jetty-distribution-9.1.0.v20131115

    【标题】"jetty-distribution-9.1.0.v20131115"指的是Jetty服务器的一个发行版本,这个版本发布于2013年11月15日。Jetty是一个开源的、轻量级的Java Web服务器和HTTP服务器,它主要被设计用于快速开发和部署Web应用...

    jetty嵌入项目实战

    本实战项目旨在帮助初学者快速掌握Jetty的嵌入式使用方法,通过实例化和配置Jetty服务器,实现Web应用的快速启动和运行。 1. **Jetty简介** - Jetty是一个开源的HTTP服务器和Servlet容器,它遵循Java Servlet和JSR...

    run-jetty-run.rar

    在"run-jetty-run"插件中,它会描述如何将Jetty集成到Eclipse的工作流中,比如创建和管理Jetty服务器实例的菜单项和快捷方式。 2. **icons**: 此目录包含了插件在Eclipse界面中使用的图标,例如启动、停止Jetty...

    jetty-6.1.26.zip

    总的来说,Jetty 6.1.26虽然相对较老,但它体现了Jetty服务器的核心设计理念和优势,如轻量级、高性能和灵活性。尽管现代开发可能更倾向于使用更新的版本,但对于了解Jetty的历史和原理,以及在某些场景下部署旧项目...

    i-jetty源码

    通过以上分析,我们可以了解到i-jetty作为一款开源的轻量级Web服务器,其源码不仅展示了Servlet容器的基本工作原理,也体现了现代Web服务器的高性能设计思路。对于Java Web开发者来说,深入研究i-jetty源码将有助于...

    spring boot内置jetty

    6. **健康检查**:Spring Boot 提供 Actuator 模块,可以监控应用的健康状态、内存使用情况、线程状态等,这对于微服务架构中的服务监控至关重要。 7. **日志管理**:Spring Boot 集成了各种日志框架,如 Logback ...

    Jetty 学习资料汇总

    1. **部署应用**:讲解如何将WAR文件或自定义配置部署到Jetty服务器。 2. **性能调优**:提供性能监控和调优的策略,包括JMX工具的使用。 3. **热部署与自动重启**:了解如何实现代码变更后应用的热部署和自动重启...

    jetty简单启动web服务第二版

    在“jetty简单启动web服务第二版”中,我们将深入探讨如何利用Jetty来运行多个Servlet,实现一个微型服务架构。在这个场景下,Jetty作为一个微服务框架,能够高效地管理和调度各个独立的服务。 1. **Jetty简介** -...

Global site tag (gtag.js) - Google Analytics