从Servlet到Tomcat再到Jetty
我们直到Servlet是处理每个HTTP请求的最小单元,而这些Servlet又被web容器所管理,不同的web容器有不同的特性和应用特点,比如常见的web 容器Tomcat和Jetty。
(另外我们还知道一些高性能web框架比如netty,和tomcat的区别是什么呢?
- tomcat是基于HTTP协议的web容器,常用来做前端请求的服务器,也支持NIO模式;
- 而netty是一个网络框架,可以基于HTTP/TCP/UTP,也可以自定义协议,自主开发不同的web服务器,以满足不同的场景需求;)
本文旨在探究web容器组织管理servlet的机制,和web容器怎么去处理不同的线程请求。
从Tomcat启动流程说起
Tomcat的系统结构:
Tomcat是一系列层级容器组成的web容器,其中最顶层的容器是Server,代表着整个服务器,从上图中可以看出,一个Server可以包含至少一个Service,用于具体提供服务;
Service容器包含的来个主要组件是Connetctor和Container;这是Tomcat最重要的两个组件:
- Connector用于处理链接相关的事情,并提供Socket与Request和Response相关的转化;
- Container用于封装和管理Servlet,以及具体处理Request请求;
这种设计模式是网络I/O常见的设计,为了能够并发处理大量连接,一般会采取一个线程方式监听客户端请求;另一个线程采用NIO的形式select已经接收到数据的channel信道,处理请求,这样的形式;
Tomcat7提供了三种Connector:Java Blocking Connector,Java Nio Blocking Connector,APR/native Connector。
- Java Blocking Connector是Tomcat7默认的网络连接处理方式,其处理模型为一个或多个线程(Acceptor)接收TCP连接,每个连接的请求都会交由单独的线程(Worker)处理。
- Java Nio Blocking Connector是Tomcat8默认的处理方式。为了避免连接过多(尤其是keepalive的连接会阻塞worker线程)造成的线程资源浪费,它在接收网络连接的线程(Acceptor)和请求处理线程(Worker)之间增加了Poller事件队列,每当有IO就绪事件都会放入Poller队列,Poller线程会不断消费队列里的事件,交由Worker线程处理。
- APR/native Connector,APR即Apache Portable Runtime Libraries,使用了native代码来优化网络请求的处理。
在容器等级中的最底层就是Context容器,负责直接管理Servlet,一个Context对应一个Web工程;
Tomcat的启动逻辑是基于观察者设计的,先来看看启动的时序图:
容器的配置属性由web.xml制定;
Tomcat处理一次http请求的时序图:
有了一个整体的了解后,让我们再来看看Tomcat是如何分发请求,以及处理多用户同时请求和管理多级容器的;
Tomcat是如何分发请求
Tomcat中使用Connector处理多线程的连接请求:
先看看Connector的主要类图:其中处理Socket的是HttpProcessor
容器的总体设计: Container是容器的父接口,所有子容器都必须实现这个接口,其中用到的设计模式是责任链模式
Engine -> Host -> Context -> Wrapper 这四个容器是个分别包含的父子关系(参见上几个图)
四个容器的关系类图:
来看看Jetty
Jetty相较于Tomcat更加轻便,虽然架构更加简单,但是看起来可并不轻松。Spring是设计初衷是用来管理应用中的实例Bean,因而是基于Bean的架构;Jetty则更倾向于流程和组件的管理,采用了基于handler的架构。handler的嵌套和链式结构,LifeCycle和doStart、doHandler模式无不印证了这点
Jetty基础架构:
Jetty与Tomcat的区别
Jetty 的架构从前面的分析可知,它的所有组件都是基于 Handler 来实现,当然它也支持 JMX。但是主要的功能扩展都可以用 Handler 来实现。可以说 Jetty 是面向 Handler 的架构,就像 Spring 是面向 Bean 的架构,iBATIS 是面向 statement 一样,而 Tomcat 是以多级容器构建起来的,它们的架构设计必然都有一个“元神”,所有以这个“元神“构建的其它组件都是肉身。
从设计模板角度来看 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 与 Jetty 的性能意义不是很大,只能说在某种使用场景下,它表现的各有差异。因为它们面向的使用场景不尽相同。从架构上来看 Tomcat 在处理少数非常繁忙的连接上更有优势,也就是说连接的生命周期如果短的话,Tomcat 的总体性能更高。
而 Jetty 刚好相反,Jetty 可以同时处理大量连接而且可以长时间保持这些连接。例如像一些 web 聊天应用非常适合用 Jetty 做服务器,像淘宝的 web 旺旺就是用 Jetty 作为 Servlet 引擎。
另外由于 Jetty 的架构非常简单,作为服务器它可以按需加载组件,这样不需要的组件可以去掉,这样无形可以减少服务器本身的内存开销,处理一次请求也是可以减少产生的临时对象,这样性能也会提高。另外 Jetty 默认使用的是 NIO 技术在处理 I/O 请求上更占优势,Tomcat 默认使用的是 BIO,在处理静态资源时,Tomcat 的性能不如 Jetty。