Jetty 的工作原理

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 创建一个ServletContextServer类,用来初始化web应用程序的Context,并且指定Servlet和Servlet匹配的url。这里指定了两个Servlet,分别是HelloServlet和GoodbyeServlet,并分别对应/hello/*和/goodbye/*。

创建一个ServletContextServer类,用来初始化web应用程序的Context,并且指定Servlet和Servlet匹配的url。这里指定了两个Servlet,分别是HelloServlet和GoodbyeServlet,并分别对应/hello/*和/goodbye/*。

public class ServletContextServer {  
    public static void main(String[] args) throws Exception {  
        Server server = new Server(8080);  
        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);  
        context.setContextPath("/");  
        server.setHandler(context);  
        // http://localhost:8080/hello  
        context.addServlet(new ServletHolder(new HelloServlet()), "/hello");  
        // http://localhost:8080/hello/kongxx  
        context.addServlet(new ServletHolder(new HelloServlet("Hello Kongxx!")), "/hello/kongxx");  
        // http://localhost:8080/goodbye  
        context.addServlet(new ServletHolder(new GoodbyeServlet()), "/goodbye");  
        // http://localhost:8080/goodbye/kongxx  
        context.addServlet(new ServletHolder(new GoodbyeServlet("Goodbye kongxx!")), "/goodbye/kongxx");  
        server.start();  
        server.join();  
    }  
}  

也可以创建两个Context,分别绑定到"/hello"和"/goodbye"

public class MultiContextServer {  
    public static void main(String[] args) throws Exception {  
        Server server = new Server(8080);  
        // http://localhost:8080/hello/kongxx  
        ServletContextHandler context1 = new ServletContextHandler(ServletContextHandler.SESSIONS);  
        context1.setContextPath("/hello");  
        context1.setResourceBase(".");  
        context1.setClassLoader(Thread.currentThread().getContextClassLoader());  
        context1.addServlet(new ServletHolder(new HelloServlet("Hello Kongxx!")), "/kongxx");  
        // http://localhost:8080/goodbye/kongxx  
        ServletContextHandler context2 = new ServletContextHandler(ServletContextHandler.SESSIONS);  
        context2.setContextPath("/goodbye");  
        context2.setResourceBase(".");  
        context2.setClassLoader(Thread.currentThread().getContextClassLoader());  
        context2.addServlet(new ServletHolder(new GoodbyeServlet("Goodbye kongxx!")), "/kongxx");  
ContextHandlerCollection contexts
= new ContextHandlerCollection(); contexts.setHandlers(new Handler[] { context1, context2 }); server.setHandler(contexts); server.start(); server.join(); } }

Jetty 的基本架构

Jetty 是一个Servlet 引擎,它的架构比较简单,也是一个可扩展性和非常灵活的应用服务器,它有一个基本数据模型,这个数据模型就是 Handler,所有可以被扩展的组件都可以作为一个 Handler,添加到 Server 中,Jetty 就是帮你管理这些 Handler。 

整个 Jetty 的核心组件由 Server 和 Connector 两个组件构成,整个 Server 组件是基于 Handler 容器工作的,Jetty 中另外一个比不可少的组件是 Connector,它负责接受客户端的连接请求,并将请求分配给一个处理队列去执行。Jetty 中还有一些可有可无的组件,我们可以在它上做扩展。如 JMX,我们可以定义一些 Mbean 把它加到 Server 中,当 Server 启动的时候,这些 Bean 就会一起工作。

整个 Jetty 的核心是围绕着 Server 类来构建,Server 类继承了 Handler,关联了 Connector 和 Container。Container 是管理 Mbean 的容器。Jetty 的 Server 的扩展主要是实现一个个 Handler 并将 Handler 加到 Server 中,Server 中提供了调用这些 Handler 的访问规则。整个 Jetty 的所有组件的生命周期管理是基于观察者模板设计,实现LifeCycle。

Handler 的体系结构

 Jetty 主要是基于 Handler 来设计的,Handler 的体系结构影响着整个 Jetty 的方方面面。下面总结了一下 Handler 的种类及作用:

Jetty 主要提供了两种 Handler 类型,一种是 HandlerWrapper,它可以将一个 Handler 委托给另外一个类去执行,如我们要将一个 Handler 加到 Jetty 中,那么就必须将这个 Handler 委托给 Server 去调用。配合 ScopeHandler 类我们可以拦截 Handler 的执行,在调用 Handler 之前或之后,可以做一些另外的事情,类似于 Tomcat 中的 Valve;另外一个 Handler 类型是 HandlerCollection,这个 Handler 类可以将多个 Handler 组装在一起,构成一个 Handler 链,方便我们做扩展。 

Jetty 的启动过程

Jetty 的入口是 Server 类,Server 类启动完成了,就代表 Jetty 能为你提供服务了。它到底能提供哪些服务,就要看 Server 类启动时都调用了其它组件的 start 方法。从 Jetty 的配置文件我们可以发现,配置 Jetty 的过程就是将那些类配置到 Server 的过程。下面是 Jetty 的启动时序图:

 

因为 Jetty 中所有的组件都会继承 LifeCycle,所以 Server 的 start 方法调用就会调用所有已经注册到 Server 的组件,Server 启动其它组件的顺序是:首先启动设置到 Server 的 Handler,通常这个 Handler 会有很多子 Handler,这些 Handler 将组成一个 Handler 链。Server 会依次启动这个链上的所有 Handler。接着会启动注册在 Server 上 JMX 的 Mbean,让 Mbean 也一起工作起来,最后会启动 Connector,打开端口,接受客户端请求,启动逻辑非常简单。

请求接受过程

Jetty 作为一个独立的 Servlet 引擎可以独立提供 Web 服务,但是它也可以与其他 Web 应用服务器集成,所以它可以提供基于两种协议工作,一个是 HTTP,一个是 AJP 协议。如果将 Jetty 集成到 Jboss 或者 Apache,那么就可以让 Jetty 基于 AJP 模式工作。下面分别介绍 Jetty 如何基于这两种协议工作,并且它们如何建立连接和接受请求的。

如果前端没有其它 web 服务器,那么 Jetty 应该是基于 HTTP 协议工作。也就是当 Jetty 接收到一个请求时,必须要按照 HTTP 协议解析请求和封装返回的数据。如果 Jboss 的前面在加一个 web 服务器,如 Apache 或者 nginx,这种架构下 servlet 引擎就不需要解析和封装返回的 HTTP 协议,因为 HTTP 协议的解析工作已经在 Apache 或 Nginx 服务器上完成了,Jboss 只要基于更加简单的 AJP 协议工作就行了。

我们设置 Jetty 的 Connector 实现类为 org.eclipse.jetty.server.bi.SocketConnector 让 Jetty 以 BIO 的方式工作(org.mortbay.jetty.nio.SelectChannelConnector),Jetty 在启动时将会创建 BIO 的工作环境,它会创建 HttpConnection 类用来解析和封装 HTTP1.1 的协议,ConnectorEndPoint 类是以 BIO 的处理方式处理连接请求,ServerSocket 是建立 socket 连接接受和传送数据,Executor 是处理连接的线程池,它负责处理每一个请求队列中任务。acceptorThread 是监听连接请求,一有 socket 连接,它将进入下面的处理流程。当 socket 被真正执行时,HttpConnection 将被调用,这里定义了如何将请求传递到 servlet 容器里,又如何将请求最终路由到目的 servlet(类似servlet的request)。 

 

Jetty 创建接受连接环境需要三个步骤:

 

  1. 创建一个队列线程池,用于处理每个建立连接产生的任务,这个线程池可以由用户来指定。
  2. 创建 ServerSocket,用于准备接受客户端的 socket 请求,以及客户端用来包装这个 socket 的一些辅助类。
  3. 创建一个或多个监听线程,用来监听访问端口是否有连接进来。

 

当建立连接的环境已经准备好了,就可以接受 HTTP 请求了,Accetptor 线程将会为这个请求创建 ConnectorEndPoint。HttpConnection 用来表示这个连接是一个 HTTP 协议的连接,它会创建 HttpParse 类解析 HTTP 协议,并且会创建符合 HTTP 协议的 Request 和 Response 对象。接下去就是将这个线程交给队列线程池去执行了。

 

 

实际上在 AJP 处理请求相比较 HTTP 时唯一的不同就是在读取到 socket 数据包时,如何来转换这个数据包,是按照 HTTP 协议的包格式来解析就是 HttpParser,按照 AJP 协议来解析就是 Ajp13Parserer。封装返回的数据也是如此。让 Jetty 工作在 AJP 协议下,需要配置 connector 的实现类为 Ajp13SocketConnector,这个类继承了 SocketConnector 类,覆盖了父类的 newConnection 方法,为的是创建 Ajp13Connection 对象而不是 HttpConnection。

请求处理过程

实际上 Jetty 的工作方式非常简单,当 Jetty 接受到一个请求时,Jetty 就把这个请求交给在 Server 中注册的代理 Handler 去执行,如何执行你注册的 Handler同样由你去规定,Jetty 要做的就是调用你注册的第一个 Handler 的 handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) 方法,接下去要怎么做,完全由你决定。

访问一个 Servlet 的代码:

 Server server = new Server(); 
 Connector connector = new SelectChannelConnector(); 
 connector.setPort(8080); 
 server.setConnectors(new Connector[]{ connector }); 
 ServletContextHandler root = new ServletContextHandler(null,"/",ServletContextHandler.SESSIONS); 
 server.setHandler(root); 
 root.addServlet(new ServletHolder(new org.eclipse.jetty.embedded.HelloServlet("Hello")),"/"); 
 server.start(); 
 server.join();

创建一个 ServletContextHandler 并给这个 Handler 添加一个 Servlet,这里的 ServletHolder 是 Servlet 的一个装饰类,它十分类似于 Tomcat 中的 StandardWrapper。

 

上图可以看出 Jetty 处理请求的过程就是 Handler 链上 handle 方法的执行过程,在这里需要解释的一点是 ScopeHandler 的处理规则,ServletContextHandler、SessionHandler 和 ServletHandler 都继承了 ScopeHandler,那么这三个类组成一个 Handler 链,它们的执行规则是:ServletContextHandler.handle->ServletContextHandler.doScope ->SessionHandler. doScope->ServletHandler. doScope->ServletContextHandler. doHandle->SessionHandler. doHandle->ServletHandler. doHandle,它这种机制使得我们可以在 doScope 做一些额外工作。

与 Tomcat 的比较

架构比较:

Jetty 的架构从前面的分析可知,它的所有组件都是基于 Handler 来实现,当然它也支持 JMX。但是主要的功能扩展都可以用 Handler 来实现。可以说 Jetty 是面向 Handler 的架构,就像 Spring 是面向 Bean 的架构,iBATIS 是面向 statement 一样,而 Tomcat 是以多级容器构建起来的,它们的架构设计必然都有一个“元神”,所有以这个“元神“构建的其它组件都是肉身。

Jetty从设计模板角度来看 Handler 的设计实际上就是一个责任链模式,接口类 HandlerCollection 可以帮助开发者构建一个链,而另一个接口类 ScopeHandler 可以帮助你控制这个链的访问顺序。另外一个用到的设计模板就是观察者模式,用这个设计模式控制了整个 Jetty 的生命周期,只要继承了 LifeCycle 接口,你的对象就可以交给 Jetty 来统一管理了。所以扩展 Jetty 非常简单,也很容易让人理解,整体架构上的简单也带来了无比的好处,Jetty 可以很容易被扩展和裁剪。

Tomcat 要臃肿很多,Tomcat 的整体设计上很复杂,前面说了 Tomcat 的核心是它的容器的设计,从 Server 到 Service 再到 engine 等 container 容器。作为一个应用服务器这样设计无口厚非,容器的分层设计也是为了更好的扩展,这是这种扩展的方式是将应用服务器的内部结构暴露给外部使用者,使得如果想扩展 Tomcat,开发人员必须要首先了解 Tomcat 的整体设计结构,然后才能知道如何按照它的规范来做扩展。这样无形就增加了对 Tomcat 的学习成本。不仅仅是容器,实际上 Tomcat 也有基于责任链的设计方式,像串联 Pipeline 的 Vavle 设计也是与 Jetty 的 Handler 类似的方式。要自己实现一个 Vavle 与写一个 Handler 的难度不相上下。表面上看,Tomcat 的功能要比 Jetty 强大,因为 Tomcat 已经帮你做了很多工作了,而 Jetty 只告诉,你能怎么做,如何做,有你去实现。

打个比方,就像小孩子学数学,Tomcat 告诉你 1+1=2,1+2=3,2+2=4 这个结果,然后你可以根据这个方式得出 1+1+2=4,你要计算其它数必须根据它给你的公式才能计算,而 Jetty 是告诉你加减乘除的算法规则,然后你就可以根据这个规则自己做运算了。所以你一旦掌握了 Jetty,Jetty 将变得异常强大。

性能比较:

单纯比较 Tomcat 与 Jetty 的性能意义不是很大,只能说在某种使用场景下,它表现的各有差异。因为它们面向的使用场景不尽相同。从架构上来看 Tomcat 在处理少数非常繁忙的连接上更有优势,也就是说连接的生命周期如果短的话,Tomcat 的总体性能更高。而 Jetty 刚好相反,Jetty 可以同时处理大量连接而且可以长时间保持这些连接。例如像一些 web 聊天应用非常适合用 Jetty 做服务器,像淘宝的 web 旺旺就是用 Jetty 作为 Servlet 引擎。

另外由于 Jetty 的架构非常简单,作为服务器它可以按需加载组件,这样不需要的组件可以去掉,这样无形可以减少服务器本身的内存开销,处理一次请求也是可以减少产生的临时对象,这样性能也会提高。另外 Jetty 默认使用的是 NIO 技术在处理 I/O 请求上更占优势,Tomcat 默认使用的是 BIO,在处理静态资源时,Tomcat 的性能不如 Jetty。

特性比较:

作为一个标准的 Servlet 引擎,它们都支持标准的 Servlet 规范,还有 Java EE 的规范也都支持,由于 Tomcat 的使用的更加广泛,它对这些支持的更加全面一些,有很多特性 Tomcat 都直接集成进来了。但是 Jetty 的应变更加快速,这一方面是因为 Jetty 的开发社区更加活跃,另一方面也是因为 Jetty 的修改更加简单,它只要把相应的组件替换就好了,而 Tomcat 的整体结构上要复杂很多,修改功能比较缓慢。所以 Tomcat 对最新的 Servlet 规范的支持总是要比人们预期的要晚。

 

目录
相关文章
|
缓存 分布式计算 API
Spark Netty与Jetty (源码阅读十一)
  spark呢,对Netty API又做了一层封装,那么Netty是什么呢~是个鬼。它基于NIO的服务端客户端框架,具体不再说了,下面开始。   创建了一个线程工厂,生成的线程都给定一个前缀名。      像一般的netty框架一样,创建Netty的EventLoopGroup:      在常用...
1098 0