读书笔记:对《活出意义来》这本书的序言特别有感触。其中:不要以成功为目标,你越是对它念念不忘,就越有可能错过它。因为成功如同幸福,不是追求就能得到;它必须因缘际会...它实际是一个人全心全意投入并把自己置之度外时,意外获得的副产品。
外界向你提供的目标,往往以某种奖励吸引着你追随。我们理应拒绝外在的奖励刺激-外驱力,需要自己建立’内奖‘-内驱力。即选定自己的目标,在追求目标的努力中,获得内心的秩序和成长的乐趣。
一、Tomcat概述
二、Tomcat核心功能
2.1 connector-网络连接器
2.1.1 三种IO模型
2.2 container-servlet 容器
2.2.1 Container的主要功能
2.2.2 Container的容器结构
三、Tomcat架构解析
四、tomcat server.xml配置说明
Tomcat是Apache基金会基于Java开发开源的web容器。Tomcat应用服务器,又称“汤姆猫”,它开源、轻量、易于集成、社区背书支持的特性,深受Java开发者偏爱。尤其是Tomcat专注于处理servlet和jsp的出色能力深得研发者信赖(当然目前jsp用的企业已经很少,瓶颈在于互联网高并发时代背景下,jsp的响应和吞吐量很难满足需求)。
web服务器除了Tomcat,还有我们熟悉的Nginx、Apache。Nginx主要用于静态资源、反向代理、负载均衡服务器。而Apache服务器主要用于静态页面和http请求,此外它有丰富的模块和插件,支持扩展实现负载均衡、虚拟主机等。
今天也是先从架构原理核心功能开头,有个大致了解,后面在对具体某个核心组件进行详细分享。
Tomcat核心功能有2个:网络连接器功能+servlet容器功能。
网络连接器功能,负责是接收客户端的http请求,并将http请求转给对应servlet进行处理,以及将servlet处理结果返回给客户端。具体是专注处理来自外部的socket连接请求,并将对应IO字节流转成对应Request和Response对象。可以说,网络连接器就是外交官,专注处理外部通讯事宜。
servlet容器功能,复杂加载和管理servlet,以及处理具体的Request请求。servlet容器就是真正的内务管家,处理内部具体业务逻辑事宜,也就是我们的接口逻辑。
这两个核心功能对应Tomcat两个核心源码组件,分别是连接器connector类,容器container接口。
connector之所以可以负责接收和响应外部的请求,是因为里面有个协议处理器ProtocolHandler,专门负责处理socket通讯。Tomcat 8.0 支持的网络协议有http/1.1还有AJP/1.3 两个协议,而Tomcat8.5之后版本则新增支持下一代http/2.0协议。
两种协议底层应用的网络IO模型,主要实现都是NIO,NIO2两个模型,此外还有一个APR模型。
三个IO模型核心特点和适用场景具体如下:
NIO ,全称Non-blocking I/O,是jdk1.4开始引入,并基于Java NIO类库的channel、buffer、selector去实现多路复用的NIO。具体是允许一个线程同时管理多个channel通道,当一个channel有读写IO事件,selector选择器再通知线程去处理,也就是允许每个线程管理多个IO操作,不像BIO是1V1那样干等,适合高并发连接数多的场景。
NIO2,全称Non-blocking I/O 2,此外也叫Asynchronous I/O,所以也称为AIO。在jdk7开始引入的异步非阻塞I/O,基于jdk 7的NIO2类库实现。具体是基于事件和回调机制,通过AsynchronousSocketChannel实现异步IO操作,解决数据复制阶段阻塞问题,允许应用程序直接返回不用阻塞。NIO2比NIO适合更高并发和复杂场景的应用,比如大数据处理、高性能网络服务器。
而APR模型(Apache Portable Runtime),是通过C/C++实现封装Unix的IO操作,可以跨平台使用,由于与操作系统底层直接交互,性能优于NIO和NIO2。适合性能要求极高的静态资源和大型web高并发请求场景。
这里附带说一下BIO,虽然BIO性能不佳,但是日常普通性能要求场景,我们还是经常使用和见到BIO。BIO是使用java.io里的FileInputStream和FileOutputStream进行读写操作,由于每次读写需要线程等待完成,导致效率低下性能不佳。
连接器里面有2个核心组件,一个是protocolHandler协议处理器,一个是Adapter适配器。源码filed视图:
ProtocolHandler接口,用来处理具体的tcp\http请求。
Adapter适配器,则将ProtocolHandler处理处理的http请求Request,转换成servlet容器能处理的servletRequest。
protocolHandler内部的核心功能,是通过Endpoint和Processor两个核心组件实现具体的协议处理:
其中Endpoint负责监听端口,实现了TCP/IP协议,专门处理socket数据流信息。
而Processor实现http协议,接收Endpoint发过来的socket字节流数据,通过http协议格式解析相关数据,转换成Tomcat的Request对象,最后通过Adapter提交到servlet容器处理。
对于协议处理器,Tomcat提供了以下7个实现类,包括我们常见的基于NIO、NIO2模型并实现http1.1协议、Ajp协议的几个处理器。
protocolHandler处理了网络连接并得到相关请求对象,为何不直接转给Container容器处理?
为什么需要Adapter适配器去中转?
由于每个协议格式数据不一样,Tomcat通过协议处理器,得到Tomcat的Request对象(org.apache.coyote.Request)。然而servlet容器里定义了自己的ServletRequest对象(HttpServletRequest),为了解耦两者通过Adapter适配器进行转换。
Adapter适配器的存在,就是为了桥接tomcat的Request对象与标准的ServletRequest对象之间的差异,确保Tomcat连接器能够顺利地与Servlet容器进行交互,同时保持系统的灵活性和扩展性。
container容器核心功能,主要负责处理网络连接器connector发过来的ServletRequest请求。
它主要的功能有:
1、负责管理Servlet的生命周期,包括Servlet的初始化、服务处理、销毁等。
2、负责http请求的调度与执行。当connector连接器接收到客户端http请求后,会将请求转给对应Container容器,Container根据请求路径和配置信息,找到对应的Servlet并调用服务方法处理请求。
3、负责响应生成HttpServletResponse对象,并返回给connector,通过connector返回给客户端。
Servlet web容器里,主要有四大组:engine、host、context、wrapper。彼此是父子层级关系,具体包含层级关系如下图:
Engine:代表Servlet引擎,一个Service只能有一个engine引擎,用来管理多个虚拟站点host。
Host:代表一个虚拟主机、站点。一个tomcat可以配置多个虚拟主机地址,然后里面可以包含多个context。
Context:表示一个web服务程序。也就是我们最熟悉的一个个应用。
wrapper:表示一个Servlet,是Container容器中最底层,没有下一级子类。
如上图所见,自上而下,有点像二叉树分散开来,tomcat通过组合模式来管理这些父子关系容器。
这四种容器,都统一实现了Container接口。他们的几个在server.xml有相关配置标签配置参数。
刚才已经对核心功能的connector、Container进行了详细介绍,接下来从整体再看。整体外部组件就比较简单。
直接看tomcat的server.xml文件可见其组成,server.xml我们非常熟悉,看起来更加直观,一目了然。
<Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"/>
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Context path="/ladingjieniu" docBase="ladingjieniu.war"/>
</Host>
</Engine>
</Service>
</Server>
server:是tomcat的最顶级容器,一个tomcat里只有一个server。server表示tomcatServlet容器以及其他组件,负责组装并启动关闭connector连接器、Servlet容器引擎。
service:一个server有多个service。service的出现,关键在于解决了网络协议解析和Servlet容器解耦问题,具体是整合多个connector连接器和Container(里面只有一个engine Servlet引擎)绑定到一起,便于两个核心功能的维护和扩展。
connector、Container就和上文说的一致。一个主要负责处理外部的tcp、http请求。另一个是具体负责Servlet请求逻辑。
彼此之间的父子层级和关联数量关系如下,一个server有多个service,一个service只有一个engine引擎,一个engine可以配置多个host虚拟站点,而一个host可以有多个context web服务,一个context web服务里有多个Servlet wrapper。
<?xml version="1.0" encoding="UTF-8"?>
<!--
Server是tomcat的最顶级容器,是server.xml的最外层标签,代表整个tomcat容器
参数port,是tomcat用来监听shutdown命令的TCP/IP端口号,默认为
8005。设置为-1,表示禁止通过端口关闭Tomcat,此时shutdown.bat/sh命令不能使用。
如果在一台服务器启动多个tomcat服务实例,该端口需保持差异。
shutdown,用来指定shutdown 命令的别名,默认值SHUTDOWN
-->
<Server port="8005" shutdown="SHUTDOWN">
<!--监听器组件,不同组件功能不一样-->
<Listener className="org.apache.catalina.startup.VersionLoggerListener"/>
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on"/>
<!--配置全局JNDI资源-->
<GlobalNamingResources>
<!-- 用户数据库Realm,配合tomcat-users.xml,管理tomcat host manager的用户身份验证 -->
<Resource name="ladingjieniu" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml"/>
</GlobalNamingResources>
<!--
一个server里有多个service,里面有一个engine和多个connector。相当于一个服务
可以配置多个connector去监听外部请求,并在engine Servlet处理。
-->
<Service name="Catalina">
<!-- 供Service内各个Connector组件共享使用的线程池执行器,我们可以定义多个命名线程池 -->
<!--
配置连接器使用连接池参数:
maxThreads:最大并发数
minSpareThreads:线程池的核心线程数
maxIdleTime:Executor中空闲线程超时关闭的毫秒数,默认1分钟
maxConnections 与tomcat建立的最大socket连接数,默认是10000
acceptCount;请求的队列最大长度,默认是100
-->
<Executor name="tomcatThreadPool" namePrefix="ladingjieniushuojishu-exec-"
maxThreads="100" minSpareThreads="50"/>
<!-- 连接器组件,负责接收和响应连接,然后创建Request和Response对象转发给engine
默认连接器,监听端口8080,并支持HTTP/1.1协议,支持指定命名共享线程池的连接器。
port:连接器监听的请求端口,必须唯一
protocol:连接器监听的请求协议类型,默认是http/1.1
redirectPort:如果当前连接器不支持SSL(HTTPS)请求,但是收到SSL(HTTPS)请求时,Catalina将重定向至指定端口的Connector。
URIEncoding:用于解码URL的字符编码,则默认使用 UTF-8。
connectionTimeout:此连接器在建立连接之后等待请求URI的超时时间(毫秒),默认值为60000(即 60秒)。
当client与tomcat建立连接之后,在"connectionTimeout"时间之内,仍然没有得到client的请求数据,此时连接将会被断开。
maxConnections:tomcat在任何给定时间能同时接收和处理的最大连接数,默认1w。
acceptCount:当同时连接数超过maxConnections时,系统会继续接收连接,并放入Accept等待队列的长度,默认是100.
同时连接数超过maxConnections,且等待队列也放满,后续收到的请求都将被直接拒绝。
-->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000" executor="tomcatThreadPool"
redirectPort="8443"/>
<!--在端口8009上定义使用AJP/1.3协议访问的连接器-->
<!--
<Connector protocol="AJP/1.3"
address="::1"
port="8009"
redirectPort="8443" />
-->
<!--
请求处理组件,一个Service标签只能有一个Engine标签
Engine组件从一个或多个Connector中接收请求并处理,并将完成的响应返回给Connector,最终响应给客户端。
-->
<Engine name="Catalina" defaultHost="localhost">
<!--
一个虚拟主机,可以指定应用程序的基本目录,包含了一个或多个Web应用
默认的虚拟主机名为"localhost",
如果配置多个Host,可以让一个tomcat支持从不同的域名访问Web应用。
autoDeploy : Tomcat 在 运行时应定期检查新的或更新的 Web 应用程序。
appBase :虚拟主机的应用程序基础目录。
unpackWARs:是否将Web应用的WAR文件解压。如果为true,则先解压然后运行解压的Web应用,如果为false,直接使用WAR文件运行Web应用。
-->
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<!--通过日志记录在该Host中处理的相关请求-->
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b"/>
</Host>
<!--
支持配置多个web服务,域名映射, 需要修改本地hosts文件 。
-->
<Host name="www.ladingjieniu.com" appBase="lading" unpackWARs="true" autoDeploy="true">
<!--
Context的path,表示该 Web 应用程序的虚拟上下文路径。
docBase,表示web应用程序的物理根目录,若该路径是相对路径的话,则是相对于appBase而言,若是绝对路径,则与appBase无关。
reloadable是否支持重新加载web应用程序类,默认为false。
如果设为true,Tomcat服务器在运行时会监视在WEB-INF/classes和Web-INF/lib目录下的class文件变化,服务器自重新加载该Web应用。
-->
<Context docBase="/app/ladingjieniu/myweb-1.0-SNAPSHOT" path="/myweb" reloadable="true" />
</Host>
</Engine>
</Service>
</Server>
推荐阅读拉丁解牛相关专题系列(欢迎交流讨论公众号搜:拉丁解牛说技术):
1、JVM进阶调优系列(5)CMS回收器通俗演义一文讲透FullGC
2、JVM进阶调优系列(4)年轻代和老年代采用什么GC算法回收?
4、JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?
6、JAVA并发编程系列(13)Future、FutureTask异步小王子
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。