Java服务容器——Jetty


发布于 2016-05-14 / 33 阅读 / 0 评论 /
Jetty 是一个开源的servlet容器,它为基于Java的web容器,例如JSP和servlet提供运行环境。

1.Jetty概述

官网描述为:Jetty is a lightweight highly scalable java based web server and servlet engine. Our goal is to support web protocols like HTTP, HTTP/2 and WebSocket in a high volume low latency way that provides maximum performance while retaining the ease of use and compatibility with years of servlet development. Jetty is a modern fully async web server that has a long history as a component oriented technology easily embedded into applications while still offering a solid traditional distribution for webapp deployment.

Jetty是使用Java语言编写的,它的API以一组JAR包的形式发布。开发人员可以将Jetty容器实例化成一个对象,可以迅速为一些独立运行的Java应用提供网络和web连接。

1.1.Jetty的特点

Jetty最突出的特点有以下两点:

(1)快速高效:Jetty是最快的Servlet服务器之一;可以处理上千个并发连接。

(2)小巧嵌入:Jetty的jar只有600多KB;可动态嵌入到应用程序,适合开发web2.0等应用。

2.Jetty基本架构

Jetty Server由多个Connector(连接器)、多个Handler(处理器),以及一个线程池组成。如下图所示:

整个Jetty的核心是围绕Server类来构建的,Server类继承了Handler,关联了Connector和Container。Container是管理Mbean的容器。

Jetty的Server的扩展主要是实现一个个Handler并将Handler加到Server中,Server中提供了调用这些Handler的访问规则。

org.eclipse.jetty.server.Server类图如下所示:

从上图中LifeCycle类的位置也可以看出:整个Jetty的所有组件的生命周期管理是基于观察者模板设计,和Tomcat的管理是类似的。

2.1.Jetty Connector组件

和Tomcat一样,Connector的主要功能是对IO模型和应用层协议的封装。

IO模型方面,最新的Jetty 9版本只支持NIO,因此Jetty的Connector设计有明显的Java NIO通信模型的痕迹。引用层协议方面,跟Tomcat的Processor一样,Jetty抽象出了Connection组件来封装应用层协议的差异。

NIO编程中的服务端在IO通信上主要完成三件事:监听连接、IO事件查询、数据读写。Jetty设计了Acceptor、SelectorManager、Connection来分别做这三件事情。

2.1.1.Acceptor

Acceptor全类名为:org.eclipse.jetty.server.AbstractConnector.Acceptor。是一个内部类,用于接收请求。同时Acceptor继承自Runnable,有独立的Acceptor线程用于处理连接请求。

for (int i = 0; i < _acceptors.length; i++) {
     Acceptor a = new Acceptor(i);
     addBean(a);
     getExecutor().execute(a);//加入全局线程池
}

Acceptor通过阻塞的方式来接受连接,如下代码所示:

public void accept(int acceptorID) throws IOException {
   ServerSocketChannel serverChannel = _acceptChannel;
   if (serverChannel != null && serverChannel.isOpen()) {
       SocketChannel channel = serverChannel.accept(); // 此处是阻塞的
       accepted(channel); // 说明有接口进来
   }
}

接受连接成功后,会调用accepted()函数,将SocketChannel设置为非阻塞模式,然后交给Selector去处理,如下代码所示:

private void accepted(SocketChannel channel) throws IOException {
     channel.configureBlocking(false);
     setSocketOption(channel, StandardSocketOptions.TCP_NODELAY, _acceptedTcpNoDelay);
     if (_acceptedReceiveBufferSize > -1)
          setSocketOption(channel, StandardSocketOptions.SO_RCVBUF, _acceptedReceiveBufferSize);
     if (_acceptedSendBufferSize > -1)
          setSocketOption(channel, StandardSocketOptions.SO_SNDBUF, _acceptedSendBufferSize);
     // _manager是org.eclipse.jetty.io.SelectorManager的实例,管理所有Selector实例
     _manager.accept(channel);
}

Acceptor的数量表示了当前jetty容器所能承受的最大并发量,由以下代码决定。

acceptors初始值由AbstractConnector类的构造函数中传入。
int cores = ProcessorUtils.availableProcessors();
if (acceptors < 0)
    acceptors = Math.max(1, Math.min(4, cores / 8));
if (acceptors > cores)
    LOG.warn("Acceptors should be <= availableProcessors: {} ", this);
_acceptors = new Thread[acceptors];

2.1.2.SelectorManager

Java NIO编程有三个关键组件:Channel、Buffer和Selector,而核心是Selector。为了方便使用,Jetty在原生Selector组件的基础上做了一些封装,实现了ManagedSelector组件。

SelectorManager的全类名为:org.eclipse.jetty.io.SelectorManager。用于管理Jetty的Selector,这些被管理的Selector叫做org.eclipse.jetty.io.ManagedSelector。

SelectorManager内部有一个ManagedSelector数组,真正干活的是ManagedSelector。

public void accept(SelectableChannel channel, Object attachment) {
     // 选择一个ManagedSelector来处理channel
     ManagedSelector selector = chooseSelector();
     // 提交一个任务Accept给ManagedSelector
     selector.submit(selector.new Accept(channel, attachment));
}

处理过程主要完成以下工作:

// 第一步:调用Selector的register方法,把channel注册到Selector上,得到一个SelectionKey
key = channel.register(selector, 0, attachment);
// 第二步:创建一个EndPoint和Connection,并跟这个SelectionKey(Channel)绑定
private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException {
     // 创建Endpoint
     EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey);
     Object context = selectionKey.attachment();
     // 创建Connection
     Connection connection = _selectorManager.newConnection(channel, endPoint, context);
     // 把Endpoint、Connection和SelectionKey绑定在一起
     endPoint.setConnection(connection);
     ……
     key.attach(endPoint);
     ……
     endPoint.onOpen();
     endPointOpened(endPoint);
     _selectorManager.connectionOpened(connection, context);
}

ManagedSelector并没有调用直接EndPoint的方法去处理数据,而是通过调用EndPoint的方法返回一个Runnable,然后把这个Runnable扔给线程池执行,这个Runnable才会去真正读数据和处理请求。这个Runnable是EndPoint的一个内部类,它会调用Connection的回调方法来处理请求。

2.1.3.Connection

Connection是一个接口,全类名为:org.eclipse.jetty.io.Connection。负责具体协议的解析,得到Request对象,并调用handler容器进行处理。类比Tomcat中的Processor。

具体实现类有org.eclipse.jetty.server.HttpConnection,下面讲讲HttpConnection的处理过程。

HttpConnection请求处理:HttpConnection并不会主动向EndPoint读取数据,而是向在EndPoint中注册一堆回调方法,数据到了就调这些回调方法_readCallback,有点异步I/O的感觉,也就是说Jetty在应用层面使用回调函数模拟了异步I/O模型。而在回调方法_readCallback里,会调用EndPoint的接口去读数据,读完后让HTTP解析器去解析字节流,HTTP解析器会将解析后的数据,包括请求行、请求头相关信息存到Request对象里。

HttpConnection响应处理:HttpConnection调用Handler进行业务处理,Handler会通过Response对象来操作响应流,向流里面写入数据,HttpConnection再通过EndPoint把数据写到Channel,这样一次响应就完成了。

2.1.4.Connector组件工作流程

Connector组件工作流程可分为以下七个步骤:

(1)Acceptor阻塞监听连接请求,当有连接请求到达时就接受连接,一个连接对应一个Channel,Acceptor将Channel交给ManagedSelector来处理。

(2)ManagedSelector把Channel注册到Selector上,并创建一个EndPoint和Connection跟这个Channel绑定,接着就不断地检测I/O事件。

(3)I/O事件到了就调用EndPoint的方法拿到一个Runnable,并扔给线程池执行。

(4)线程池中调度某个线程执行Runnable。

(5)Runnable执行时,调用回调函数,这个回调函数是Connection注册到EndPoint中的,即Endpoint通知Connection

(6)回调函数内部实现,其实就是调用EndPoint的接口方法来读数据,即Connection收到通知后去EndPoint读数据

(7)Connection解析读到的数据,生成请求对象并交给Handler组件去处理。

在线程模型设计上Tomcat的NioEndpoint跟Jetty的Connector是相似的,都是用一个Acceptor数组监听连接,用一个Selector数组侦测I/O事件,用一个线程池执行请求。它们的不同点在于,Jetty使用了一个全局的线程池,所有的线程资源都是从线程池来分配。

Jetty Connector设计中的一大特点是,使用了回调函数来模拟异步I/O,比如Connection向EndPoint注册了一堆回调函数。它的本质将函数当作一个参数来传递,告诉对方,你准备好了就调这个回调函数。

2.2.Jetty Handler组件

Handler是一个接口,全类名为:org.eclipse.jetty.server.Handler。

public interface Handler extends LifeCycle, Destroyable {
     // 处理请求的方法,蕾丝Tomcat容器组件的service方法
     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
     // 每个Handler都关联一个Server组件,被Server管理
     public void setServer(Server server);
     public Server getServer();
     // 销毁方法相关的资源
     public void destroy();
}

Handler组件是Jetty的灵魂,Connector会将Request请求交给Handler去处理。

Jetty 主要提供了两种 Handler 类型。

2.2.1.HandlerWrapper

它可以将一个 Handler 委托给另外一个类去执行,如我们要将一个 Handler 加到 Jetty 中,那么就必须将这个 Handler 委托给 Server 去调用。配合 ScopeHandler 类我们可以拦截 Handler 的执行,在调用 Handler 之前或之后,可以做一些另外的事情,类似于 Tomcat 中的 Valve。

2.2.2.HandlerCollection

这个 Handler 类可以将多个 Handler 组装在一起,构成一个 Handler 链,方便我们做扩展。

HandlerWrapper和HandlerCollection都是Handler,但是这些Handler里还包括其他Handler的引用。不同的是,HandlerWrapper只包含一个其他Handler的引用,而HandlerCollection中有一个Handler数组的引用。

HandlerCollection的Handler数组分成三种类型:

(1)第一种是协调Handler,这种Handler负责将请求路由到一组Handler中去,比如上图中的HandlerCollection,它内部持有一个Handler数组,当请求到来时,它负责将请求转发到数组中的某一个Handler。

(2)第二种是过滤器Handler,这种Handler自己会处理请求,处理完了后再把请求转发到下一个Handler,比如图上的HandlerWrapper,它内部持有下一个Handler的引用。需要注意的是,所有继承了HandlerWrapper的Handler都具有了过滤器Handler的特征,比如ContextHandler、SessionHandler和WebAppContext等。

(3)第三种是内容Handler,这些Handler会真正调用Servlet来处理请求,生成响应的内容,比如ServletHandler。如果浏览器请求的是一个静态资源,也有相应的ResourceHandler来处理这个请求,返回静态页面。

3.Jetty Server启动过程

Jetty的入口是org.eclipse.jetty.server.Server,Server类启动完成就代表Jetty可对外提供服务。具体提供了哪些服务,需要看Server启动过程中都调用了哪些组件的start方法,这些组件在Jetty的配置文件中配置。

Jetty Server启动过程如下图所示:

Jetty中所有的组件都会继承LifeCycle接口,Server的doStart方法调用就会调用所有已经注册到Server的组件。

Server启动其他组件的顺序是:首先启动设置到Server的Handler,通常这个Handler会有很多子Handler,这些Handler将组成一个Handler链,Server会依次启动这个链上的所有Handler;然后会启动注册到Server上的JMX的Mbean,让Mbean也一起工作起来;最后会启动Connector,打开端口,接受客户端请求。

4.Jetty接收请求过程

Jetty接收请求过程其实就是Connector接收请求过程,可参考《Connector组件工作流程》章节内容。

请求接收过程如下图所示:

请求处理过程由ManagerSelector.Accept类来实现。

5.Spring-Boot集成Jetty

Tomcat是SpringBoot默认的容器技术,同时SpringBoot也支持Jetty容器,而Jetty的性能和内存使用方面都优于Tomcat,弱于Undertow。

maven依赖如下所示:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <!-- Exclude the Tomcat dependency -->
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- Use Jetty instead -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
</dependencies>