前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自家表兄弟Tomcat和Jetty

自家表兄弟Tomcat和Jetty

作者头像
春哥大魔王
发布2020-03-13 02:02:45
1.3K0
发布2020-03-13 02:02:45
举报

Jetty是Eclipse基金会的一个开源项目,是“HTTP服务器 + Servlet容器”,并且Jetty和Tomcat在架构设计上有不少相似的地方,实在是像一对表兄弟。

但同时Jetty也有自己的特点,主要是更加小巧,更易于定制化。Jetty作为一名后起之秀,应用范围也越来越广,Jetty来作为Web容器已经在大量CNCF技术体系下发展的红红火火。

两者都是符合Servlet标准,功能上几乎一样,但是由于诞生的时代背景不同,所以很多对标的其他解决方案也不同。

Tomcat对标的是Apache,Iis都是五大三粗的全面的web server。Jetty后来居上主要是也站在了Tomcat老大哥的肩膀上,少走了好多弯路。如果非要说两者有什么不同,可以认为是不同组织开发的,架构实现上有不同。

Jetty架构上相比于Tomcat更简单,Jetty架构是基于Handler实现的,扩展也是基于Handler实现,扩展简单,Tomcat面向的容器设计,如果扩展需要了解整个Tomcat架构,扩展上较为复杂。

Jetty可以同时处理大量连接,且可以保持长时间连接,适用于Web聊天室之类的应用,Tomcat适合处理少数且繁忙的连接,比如一些生命周期较短的,Tomcat有一定的性能优势。

先看下Jetty的整体架构,Jetty Server就是由多个Connector(连接器)、多个Handler(处理器),以及一个线程池组成。

Jetty和Tomcat一样实现了Servlet容器和Jsp的相应规范。Jetty中的Connector组件和Handler组件分别来实现Http服务器和Servlet容器的功能,而这两个组件工作时所需要的线程资源都直接从一个全局线程池ThreadPool中获取。

Jetty Server可以有多个Connector在不同的端口上监听客户请求,而对于请求处理的Handler组件,也可以根据具体场景使用不同的Handler。

这样的设计提高了Jetty的灵活性,需要支持Servlet,则可以使用ServletHandler;需要支持Session,则再增加一个SessionHandler。如果不使用Servlet或者Session,只要不配置这个Handler就行了。

Jetty提供了一个Server类创建并初始化Connector、Handler、ThreadPool组件,然后调用start方法启动它们。

Tomcat和Jetty有什么相同又有什么不同呢?

1)第一个区别是Jetty中没有Service的概念,Tomcat中的Service包装了多个连接器和一个容器组件,一个Tomcat实例可以配置多个Service,不同的Service通过不同的连接器监听不同的端口;而Jetty中Connector是被所有Handler共享的。

2)第二个区别是,在Tomcat中每个连接器都有自己的线程池,而在Jetty中所有的Connector共享一个全局的线程池。

Connector的主要功能是对I/O模型和应用层协议的封装。I/O模型方面Jetty 9版本只支持NIO,因此Jetty的Connector设计有明显的Java NIO通信模型的痕迹。应用层协议方面,跟Tomcat的Processor一样,Jetty抽象出了Connection组件来封装应用层协议的差异。

Java NIO的核心组件是Channel、Buffer和Selector。

Channel表示一个连接,可以理解为一个Socket,通过它可以读取和写入数据,但是并不能直接操作数据,需要通过Buffer来中转。

Selector可以用来检测Channel上的I/O事件,比如读就绪、写就绪、连接就绪,一个Selector可以同时处理多个Channel,因此单个线程可以监听多个Channel,这样会大量减少线程上下文切换的开销。

典型的NIO程序实现逻辑如下:

1.创建服务端Channel,绑定监听端口并把Channel设置为非阻塞方式。

2.创建Selector,并在Selector中注册Channel感兴趣的事件OP_ACCEPT,告诉Selector如果客户端有新的连接请求到这个端口就通知我。

3.Selector会在一个死循环里不断地调用select()去查询I/O状态,select()会返回一个SelectionKey列表,Selector会遍历这个列表,看看是否有“客户”感兴趣的事件,如果有,就采取相应的动作。

上面的例子说明,如果有新的连接请求,就会建立一个新的连接。连接建立后,再注册Channel的可读事件到Selector中,告诉Selector我对这个Channel上是否有新的数据到达感兴趣。

服务端在I/O通信上主要完成了三件事情:监听连接、I/O事件查询以及数据读写。Jetty设计了Acceptor、SelectorManager和Connection来分别做这三件事情,下面我分别来说说这三个组件。

Acceptor用于接受请求,跟Tomcat一样,Jetty也有独立的Acceptor线程组用于处理连接请求。在Connector的实现类ServerConnector中,有一个_acceptors的数组,在Connector启动的时候, 会根据_acceptors数组的长度创建对应数量的Acceptor,而Acceptor的个数可以配置。

Acceptor是ServerConnector中的一个内部类,同时也是一个Runnable,Acceptor线程是通过getExecutor()得到的线程池来执行的,前面提到这是一个全局的线程池。

Acceptor通过阻塞的方式来接受连接,这一点跟Tomcat也是一样的。

接受连接成功后会调用accepted()函数,accepted()函数中会将SocketChannel设置为非阻塞模式,然后交给Selector去处理,因此这也就到了Selector的地界了。

Jetty的Selector由SelectorManager类管理,而被管理的Selector叫作ManagedSelector。SelectorManager内部有一个ManagedSelector数组,真正干活的是ManagedSelector。接着上面分析,看看在SelectorManager在accept方法里做了什么。

SelectorManager从本身的Selector数组中选择一个Selector来处理这个Channel,并创建一个任务Accept交给ManagedSelector,ManagedSelector在处理这个任务主要做了两步:

第一步,调用Selector的register方法把Channel注册到Selector上,拿到一个SelectionKey。

第二步,创建一个EndPoint和Connection,并跟这个SelectionKey(Channel)绑在一起:

上面这两个过程是什么意思呢?打个比方,你到餐厅吃饭,先点菜(注册I/O事件),服务员(ManagedSelector)给你一个单子(SelectionKey),等菜做好了(I/O事件到了),服务员根据单子就知道是哪桌点了这个菜,于是喊一嗓子某某桌的菜做好了(调用了绑定在SelectionKey上的EndPoint的方法)。

这里需要你特别注意的是,ManagedSelector并没有调用直接EndPoint的方法去处理数据,而是通过调用EndPoint的方法返回一个Runnable,然后把这个Runnable扔给线程池执行,所以你能猜到,这个Runnable才会去真正读数据和处理请求。

Connection这个Runnable是EndPoint的一个内部类,它会调用Connection的回调方法来处理请求。Jetty的Connection组件类比就是Tomcat的Processor,负责具体协议的解析,得到Request对象,并调用Handler容器进行处理。下面简单介绍一下它的具体实现类HttpConnection对请求和响应的处理过程。

请求处理:HttpConnection并不会主动向EndPoint读取数据,而是向在EndPoint中注册一堆回调方法:

告诉EndPoint,数据到了你就调我这些回调方法_readCallback吧,有点异步I/O的感觉,也就是说Jetty在应用层面模拟了异步I/O模型。

而在回调方法_readCallback里,会调用EndPoint的接口去读数据,读完后让HTTP解析器去解析字节流,HTTP解析器会将解析后的数据,包括请求行、请求头相关信息存到Request对象里。

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

再来回顾一下Connector的工作流程。

  • step1.Acceptor监听连接请求,当有连接请求到达时就接受连接,一个连接对应一个Channel,Acceptor将Channel交给ManagedSelector来处理。
  • step2.ManagedSelector把Channel注册到Selector上,并创建一个EndPoint和Connection跟这个Channel绑定,接着就不断地检测I/O事件。
  • step3.I/O事件到了就调用EndPoint的方法拿到一个Runnable,并扔给线程池执行。
  • step4.线程池中调度某个线程执行Runnable。
  • step5.Runnable执行时,调用回调函数,这个回调函数是Connection注册到EndPoint中的。
  • step6.回调函数内部实现,其实就是调用EndPoint的接口方法来读数据。
  • step7.Connection解析读到的数据,生成请求对象并交给Handler组件去处理。

Jetty Server就是由多个Connector、多个Handler,以及一个线程池组成,在设计上简洁明了。

Jetty的Connector只支持NIO模型,跟Tomcat的NioEndpoint组件一样,它也是通过Java的NIO API实现的。Java NIO编程有三个关键组件:Channel、Buffer和Selector,而核心是Selector。为了方便使用,Jetty在原生Selector组件的基础上做了一些封装,实现了ManagedSelector组件。

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

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

Tomcat和Jetty相比,Jetty的I/O线程模型更像Netty,Jetty的EatWhatYouKill线程策略,其实就是Netty 4.0中的线程模型。

Ask:

  • Jetty的Connector主要完成了三件事件:接收连接、I/O事件查询以及数据读写。因此Jetty设计了Acceptor、SelectorManager和Connection来做这三件事情。为什么要把这些组件跑在不同的线程里呢?

Ans:

  • 反过来想,如果等待连接到达,接收连接、等待数据到达、数据读取和请求处理(等待应用处理完)都在一个线程里,这中间线程可能大部分时间都在”等待“,没有干活,而线程资源是很宝贵的。并且线程阻塞会发生线程上下文切换,浪费CPU资源。

Connector会将Servlet请求交给Handler去处理,Jetty的Handler在设计上非常有意思,可以说是Jetty的灵魂,Jetty通过Handler实现了高度可定制化。

Handler就是一个接口,它有一堆实现类,Jetty的Connector组件调用这些接口来处理Servlet请求,先来看看这个接口定义成什么样子。

Handler接口的定义非常简洁,主要就是用handle方法用来处理请求,跟Tomcat容器组件的service方法一样,它有ServletRequest和ServeletResponse两个参数。除此之外,这个接口中还有setServer和getServer方法,因为任何一个Handler都需要关联一个Server组件,也就是说Handler需要被Server组件来管理。一般来说Handler会加载一些资源到内存,因此通过设置destroy方法来销毁。

Handler只是一个接口,完成具体功能的还是它的子类。那么Handler有哪些子类呢?它们的继承关系又是怎样的?这些子类是如何实现Servlet容器功能的呢?

Jetty中定义了一些默认Handler类,并且这些Handler类之间的继承关系比较复杂,先通过一个全景图来了解一下。对类图进行了简化。

从图可以看到,Handler的种类和层次关系还是比较复杂的:

Handler接口之下有抽象类AbstractHandler,这一点并不意外,因为有接口一般就有抽象实现类。

在AbstractHandler之下有AbstractHandlerContainer,为什么需要这个类呢?这其实是个过渡,为了实现链式调用,一个Handler内部必然要有其他Handler的引用,所以这个类的名字里才有Container,意思就是这样的Handler里包含了其他Handler的引用。

理解了上面的AbstractHandlerContainer,就能理解它的两个子类了:HandlerWrapper和HandlerCollection。简单来说就是,HandlerWrapper和HandlerCollection都是Handler,但是这些Handler里还包括其他Handler的引用。不同的是,HandlerWrapper只包含一个其他Handler的引用,而HandlerCollection中有一个Handler数组的引用。

接着来看左边的HandlerWrapper,它有两个子类:Server和ScopedHandler。Server比较好理解,它本身是Handler模块的入口,必然要将请求传递给其他Handler来处理,为了触发其他Handler的调用,所以它是一个HandlerWrapper。

再看ScopedHandler,它也是一个比较重要的Handler,实现了“具有上下文信息”的责任链调用。为什么要强调“具有上下文信息”呢?那是因为Servlet规范规定Servlet在执行过程中是有上下文的。那么这些Handler在执行过程中如何访问这个上下文呢?这个上下文又存在什么地方呢?答案就是通过ScopedHandler来实现的。

而ScopedHandler有一堆的子类,这些子类就是用来实现Servlet规范的,比如ServletHandler、ContextHandler、SessionHandler、ServletContextHandler和WebAppContext。

请看类图的右边,跟HandlerWapper对等的还有HandlerCollection,HandlerCollection其实维护了一个Handler数组。为什么要发明一个这样的Handler?这是因为Jetty可能需要同时支持多个Web应用,如果每个Web应用有一个Handler入口,那么多个Web应用的Handler就成了一个数组,比如Server中就有一个HandlerCollection,Server会根据用户请求的URL从数组中选取相应的Handler来处理,就是选择特定的Web应用来处理请求。

虽然从类图上看Handler有很多,但是本质上这些Handler分成三种类型:

  • 第一种是协调Handler,这种Handler负责将请求路由到一组Handler中去,比如上图中的HandlerCollection,它内部持有一个Handler数组,当请求到来时,它负责将请求转发到数组中的某一个Handler。
  • 第二种是过滤器Handler,这种Handler自己会处理请求,处理完了后再把请求转发到下一个Handler,比如图上的HandlerWrapper,它内部持有下一个Handler的引用。需要注意的是,所有继承了HandlerWrapper的Handler都具有了过滤器Handler的特征,比如ContextHandler、SessionHandler和WebAppContext等。
  • 第三种是内容Handler,说白了就是这些Handler会真正调用Servlet来处理请求,生成响应的内容,比如ServletHandler。如果浏览器请求的是一个静态资源,也有相应的ResourceHandler来处理这个请求,返回静态页面。

ServletHandler、ContextHandler以及WebAppContext等,它们实现了Servlet规范,那具体是怎么实现的呢?先来看看如何使用Jetty来启动一个Web应用。

上面的过程主要分为两步:

第一步创建一个WebAppContext,接着设置一些参数到这个Handler中,就是告诉WebAppContextWAR包放在哪,Web应用的访问路径是什么。

第二步就是把新创建的WebAppContext添加到Server中,然后启动Server。

WebAppContext对应一个Web应用。Servlet规范中有Context、Servlet、Filter、Listener和Session等,Jetty要支持Servlet规范,就需要有相应的Handler来分别实现这些功能。因此,Jetty设计了3个组件:ContextHandler、ServletHandler和SessionHandler来实现Servle规范中规定的功能,而WebAppContext本身就是一个ContextHandler,另外它还负责管理ServletHandler和SessionHandler。

再来看一下什么是ContextHandler。ContextHandler会创建并初始化Servlet规范里的ServletContext对象,同时ContextHandler还包含了一组能够让Web应用运行起来的Handler,可以这样理解,Context本身也是一种Handler,它里面包含了其他的Handler,这些Handler能处理某个特定URL下的请求。比如,ContextHandler包含了一个或者多个ServletHandler。

再来看ServletHandler,它实现了Servlet规范中的Servlet、Filter和Listener的功能。ServletHandler依赖FilterHolder、ServletHolder、ServletMapping、FilterMapping这四大组件。FilterHolder和ServletHolder分别是Filter和Servlet的包装类,每一个Servlet与路径的映射会被封装成ServletMapping,而Filter与拦截URL的映射会被封装成FilterMapping。

SessionHandler从名字就知道它的功能,用来管理Session。除此之外WebAppContext还有一些通用功能的Handler,比如SecurityHandler和GzipHandler,同样从名字可以知道这些Handler的功能分别是安全控制和压缩/解压缩。

WebAppContext会将这些Handler构建成一个执行链,通过这个链会最终调用到业务Servlet。通过一张图来理解一下。

通过对比Tomcat的架构图,Jetty的Handler组件和Tomcat中的容器组件是大致是对等的概念,Jetty中的WebAppContext相当于Tomcat的Context组件,都是对应一个Web应用;而Jetty中的ServletHandler对应Tomcat中的Wrapper组件,它负责初始化和调用Servlet,并实现了Filter的功能。

对于一些通用组件,比如安全和解压缩,在Jetty中都被做成了Handler,这是Jetty Handler架构的特点。

因此对于Jetty来说,请求处理模块就被抽象成Handler,不管是实现了Servlet规范的Handler,还是实现通用功能的Handler,比如安全、解压缩等,可以任意添加或者裁剪这些“功能模块”,从而实现高度的可定制化。

Jetty Server就是由多个Connector、多个Handler,以及一个线程池组成。

Jetty的Handler设计是它的一大特色,Jetty本质就是一个Handler管理器,Jetty本身就提供了一些默认Handler来实现Servlet容器的功能,你也可以定义自己的Handler来添加到Jetty中,这体现了“微内核 + 插件”的设计思想。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 春哥talk 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档