1. 为什么有servlet容器
前面讲到了servlet是一种规范,不能够独立提供服务,需要被部署到容器内部,由servlet容器来管理和运行servlet。servlet容器提供功能:
- 生命周期管理:容器提供了servlet的运行环境,控制生命周期,负责加载、初始化和销毁servlet。
- 通信支持:容器封装了http协议、端口监听、流创建读取等功能,可以是开发者更集中在servlet中service方法逻辑。
- 多线程支持:容器在接收请求后自动创建多线程处理;
2. servlet容器分类介绍
先明白几个基本概念:
2.1 web服务器和web容器
用一张清晰的图片呈现:
图来自:各种容器与服务器的区别与联系:Servlet容器、WEB容器、Java EE容器、应用服务器、WEB服务器、Java EE服务器 - ppjj - 博客园
- 服务器是提供计算服务的设备;
- 容器屏蔽了服务器平台的复杂性,为部署在容器内的应用程序提供运行环境;
所以容器是位于应用程序/组件和服务器平台之间的接口集合,使得应用程序/组件可以方便部署到服务器上运行。
- WEB容器:可以部署多个WEB应用程序的环境。
- WEB服务器:一般指网站服务器,可以向浏览器等WEB客户端提供文档浏览、数据文件下载等WEB服务。
2.2 web 容器和servlet容器
?图来自:各种容器与服务器的区别与联系:Servlet容器、WEB容器、Java EE容器、应用服务器、WEB服务器、Java EE服务器 - ppjj - 博客园
web容器是管理servlet(通过servlet容器),以及监听器(Listener)和过滤器(Filter)的。没有servlet容器,也可以用web容器提供静态页面访问,比如安装一个apache等。web容器主要有:Apache、IIS、Tomcat、Jetty、JBoss、webLogic等,而Tomcat、Jetty、JBoss、webLogic同时也是servlet容器,或者说他们还包含了servlet容器。
几种常见web容器的比较:
Tomcat |
- 是Apache下一个免费的开放源代码的Web 应用服务器;
- 运行时占用的系统资源小,扩展性好,支持负载平衡与邮件服务等开发应用系统常用的功能;
- 适用于中小型系统和并发访问用户不是很多的场合
| Jboss |
- JBoss是免费的,开放源代码J2EE的实现,它通过LGPL许可证进行发布
-
安装非常简单,JBoss需要的内存和硬盘空间比较小; -
JBoss能够"热部署",部署BEAN只是简单拷贝BEAN的JAR文件到部署路径下就可以; -
JBoss与Web服务器在同一个Java虚拟机中运行,Servlet调用EJB不经过网络,从而大大提高运行效率,提升安全性能。
| Weblogic |
- 美国bea公司出品的基于j2ee架构的中间件,纯Java代码开发的;
- 用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器;
- 支持业内多种标准,可扩展性强,包括客户机连接的共享、资源pooling以及动态网页和EJB组件群集;
- 部署灵活,有很高的可靠性
| WebSphere |
| Resin |
|
3. Tomcat容器
Tomcat是一个JSP/Servlet容器,也是一个web容器。作为一个servlet容器,有三种工作模式:
- 独立的servlet容器:servlet容器是web服务器的一部分
- 进程内的servlet容器:servlet容器是作为web服务器的插件和java容器的实现,web服务器插件在内部地址空间打开一个jvm使得java容器在内部得以运行。反应速度快但伸缩性不足
- 进程外的servlet容器,servlet容器运行于web服务器之外的地址空间,并作为web服务器的插件和java容器实现的结合。反应时间不如进程内但伸缩性和稳定性比进程内优;
根据进入tomcat容器的请求,工作模式分为如下两类:
- 应用程序服务器:请求来自于前端的web服务器,这可能是Apache, IIS, Nginx等;
- 独立服务器:请求来自于web浏览器;?
解压tomcat,目录结构有:
tomcat: ?? ?|---bin:存放启动和关闭tomcat脚本 ?? ?|---conf:存放不同的配置文件(server.xml和web.xml) ? ? |---doc:存放Tomcat文档; |---lib/japser/common:存放Tomcat运行需要的库文件(JARS); |---logs:存放Tomcat执行时的LOG文件; |---src:存放Tomcat的源代码; |---webapps:Tomcat的主要Web发布目录(包括应用程序示例); |---work:存放jsp编译后产生的class文件;
顶层结构:
Tomcat中最顶层的容器是Server,一个Server可以包含多个Service,一个Service只有一个Engine,但是可以有多个Connectors。Connector用于处理连接相关的事情,并提供Socket与Request和Response相关的转化;Engine用于封装和管理Servlet,以及具体处理Request请求;
这种架构设计的优点:
- 各组件模块化相互配合独立,具备良好的扩展性
- 允许子容器集成服容器的配置,简化配置;
- 便于组件的生命周期管理(每个组件管理生命周期并通知其子节点)
tomcat的核心组件
server.xml位于$TOMCAT_HOME/conf目录下;
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JasperListener" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" 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>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context />
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log." suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
3.1 顶级组件server
Server表示正在运行的Tomcat实例,可包含一个或多个Service子容器;
图片来自:Tomcat整体架构分析 - 灌木大叔 - 博客园?
JNDI(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。JNDI已经成J2EE的标准之一,所有的J2EE容器都必须提供一个JNDI服务。
Naming,所谓名称服务,简单来说就是通过名称查找实际对象的服务;
Directory,是一种特殊的名称服务,目录服务(Directory Service)提供了对目录中对象(directory objects)的属性进行增删改查的操作。比如命名服务中根据打印机名称去获取打印机对象(引用),然后进行打印操作;同时打印机拥有速率、分辨率、颜色等属性,作为目录服务,用户可以根据打印机的分辨率去搜索对应的打印机对象。
3.2 connector?
Connector的主要功能,是接收连接请求,创建Request和Response对象用于和请求端交换数据;然后分配线程让Engine来处理这个请求,并把产生的Request和Response对象传给Engine。?通过配置Connector,可以控制请求Service的协议及端口号. 如上配置:
# 默认BIO模式
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
#默认NIO模式
<Connector port="9090" protocol="org.apache.coyote.http11.Http11NioProtocol" connectionTimeout="20000" redirectPort="8443" />
#默认APR模式
<Connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol"
connectionTimeout="20000" redirectPort="8443" />
?Tomcat Connector(Tomcat连接器)有bio、nio、apr三种运行模式。
模式 | 默认运行版本 | 处理方式 |
---|
BIO运行模式 | Tomcat7或以下版本 | 一个线程处理一个请求;缺点:并发量高是,线程数较多,浪费资源 | NIO运行模式 | Tomcat8版本 | 利用Java的异步IO处理,可通过少量的线程处理大量请求; | APR运行模式 | Tomcat7 或 8 在win7或以上系统中默认使用 | APR是使用原生C语言编写的非堵塞I/O,利用了操作系统的网络连接功能,速度很快; 以JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作; 需先安装apr和native; |
3.3 Engine
Engine组件从一个或多个Connector中接收请求并处理,并将完成的响应返回给Connector,最终传递给客户端。
<Engine name="Catalina" defaultHost="localhost">
3.3.1 HOST
????????Host是Engine的子容器。Engine组件中可以内嵌1个或多个Host组件,每个Host组件代表Engine中的一个虚拟主机。Host组件至少有一个,且其中一个的name必须与Engine组件的defaultHost属性相匹配。
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
3.3.2 Context
????????Context是Host的子容器,每个Host中可以定义任意多的Context元素,Context元素代表在特定虚拟主机上运行的一个Web应用。
Tomcat可以开启自动部署,当Web应用没有在server.xml中配置静态部署,可以由Tomcat通过特定的规则自动部署。要开启Web应用的自动部署,需要配置所在的虚拟主机deployOnStartup和autoDeploy属性。deployOnStartup为true时,Tomcat在启动时检查Web应用,且检测到的所有Web应用视作新应用;autoDeploy为true时,Tomcat在运行时定期检查新的Web应用或Web应用的更新。
????????除了自动部署,也可以在server.xml中通过<context>元素静态部署Web应用。静态部署与自动部署是可以共存的。在实际应用中,并不推荐使用静态部署,因为server.xml 是不可动态重加载的资源,服务器一旦启动了以后,要修改这个文件,就得重启服务器才能重新加载。而自动部署可以在Tomcat运行时通过定期的扫描来实现,不需要重启服务器。
server.xml中使用Context元素配置Web应用,Context元素应该位于Host元素中。举例如下:
<Context path="/" docBase="/home/admin/app/app1.war" reloadable="true"/>
docBase:静态部署时,docBase可以在appBase目录下,也可以不在;
path:静态部署时,可以显式指定path属性,但是仍然受到了严格的限制:只有当自动部署完全关闭(deployOnStartup和autoDeploy都为false)或docBase不在appBase中时,才可以设置path属性。
tomcat的三种部署模式:
自动部署 | 复制war包到tomcat的webapps目录中 | 增加web部署文件 | | 在server.xml中,在<Host/> 节点中添加一个context | <Context Path="/test"Docbase="E:\workPlace-2019\test" Debug="0" Reloadable="True"></Context> |
3.3.3?Valve
valve是处理元素,它可以被包含在每个Tomcat容器的处理路径中--如engine、host、context以及servelt包装器。若要增加Valve到Tomcat容器则需要在server.xml中使用<Valve>标签。在server.xml中这些标签的执行顺序与其物理顺序相同。
3.4 tomcat的类加载机制
3.4.1 tomcat为什么要打破双亲委派机制
- 一个web容器可以部署多个应用程序,不同应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离。
- 部署在同一个web容器中相同的类库相同的版本可以共享。
- web容器也有自己依赖的类库,不能与应用程序的类库混淆,应该让容器的类库和程序的类库隔离开来;
如果使用Java自带的类加载器,就不能加载两个相同类库的不同版本。
3.4.2 tomcat类加载器
图片来自:Tomcat怎么打破双亲委派机制的_Jackyyl729的博客-CSDN博客_tomcat打破?
- commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
- catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
- sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
- WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;
WebAppClassLoader中,查找class方法源码:
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
...
Class<?> clazz = null;
try {
try {
if (securityManager != null) {
PrivilegedAction<Class<?>> dp =
new PrivilegedFindClassByName(name);
clazz = AccessController.doPrivileged(dp);
} else {
// 先在应用内找
clazz = findClassInternal(name);
}
} catch(AccessControlException ace) {
log.warn(sm.getString("webappClassLoader.securityException", name,
ace.getMessage()), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
// 委托父类找
if ((clazz == null) && hasExternalRepositories) {
try {
clazz = super.findClass(name);
} catch(AccessControlException ace) {
log.warn(sm.getString("webappClassLoader.securityException", name,
ace.getMessage()), ace);
throw new ClassNotFoundException(name, ace);
} catch (RuntimeException e) {
if (log.isTraceEnabled())
log.trace(" -->RuntimeException Rethrown", e);
throw e;
}
}
if (clazz == null) {
if (log.isDebugEnabled())
log.debug(" --> Returning ClassNotFoundException");
throw new ClassNotFoundException(name);
}
} catch (ClassNotFoundException e) {
if (log.isTraceEnabled())
log.trace(" --> Passing on ClassNotFoundException");
throw e;
}
...
return clazz;
}
加载类的实现:
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
// (0) Check our previously loaded local class cache
clazz = findLoadedClass0(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// (0.1) Check our previously loaded class cache
clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
if (clazz != null) {
if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
// (0.2) Try loading the class with the system class loader, to prevent
// the webapp from overriding Java SE classes.
String resourceName = binaryNameToPath(name, false);
ClassLoader javaseLoader = getJavaseClassLoader();
boolean tryLoadingFromJavaseLoader;
try {
URL url;
if (securityManager != null) {
PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
url = AccessController.doPrivileged(dp);
} else {
url = javaseLoader.getResource(resourceName);
}
tryLoadingFromJavaseLoader = (url != null);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
tryLoadingFromJavaseLoader = true;
}
// 核心类加载器加载
if (tryLoadingFromJavaseLoader) {
try {
clazz = javaseLoader.loadClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (0.5) Permission to access this class when using a SecurityManager
if (securityManager != null) {
int i = name.lastIndexOf('.');
if (i >= 0) {
try {
securityManager.checkPackageAccess(name.substring(0,i));
} catch (SecurityException se) {
String error = sm.getString("webappClassLoader.restrictedPackage", name);
log.info(error, se);
throw new ClassNotFoundException(error, se);
}
}
}
boolean delegateLoad = delegate || filter(name, true);
// (1) Delegate to our parent if requested,委托给父类
if (delegateLoad) {
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
// (2) Search local repositories
try {
clazz = findClass(name);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
// (3) Delegate to parent unconditionally,自己加载
if (!delegateLoad) {
try {
clazz = Class.forName(name, false, parent);
if (clazz != null) {
if (resolve)
resolveClass(clazz);
return clazz;
}
} catch (ClassNotFoundException e) {
// Ignore
}
}
}
throw new ClassNotFoundException(name);
}
4. tomcat调优
4.1 内存调优
Tomcat运行在JVM上,所以存在JVM调优。修改TOMCAT_HOME/bin/catalina.sh JAVA_OPTS="-server -XX:PermSize=512M -XX:MaxPermSize=1024m -Xms2048m -Xmx2048m
4.2 连接器调优
4.2.1 连接器线程池调优
4.2.2 IO 调优
Tomcat8以上版本,默认使用的就是NIO模式,如果是BIO更改为NIO。apr是Tomcat生产环境运行的首选方式.
4.2.3?禁用AJP
4.3 动静分离
将静态资源从Tomcat分离,交由Nginx处理。
4.4 禁用DNS解析
修改server.xml文件中的enableLookups参数值改为false:enableLookups="false"。
参考文献:
几种常见web 容器比较 - kaleidoscopic - 博客园?
Tomcat原理详解及请求过程 - 奋斗de程序猿 - 博客园
详解Tomcat 配置文件server.xml - 编程迷思 - 博客园
Tomcat的三种运行模式_Jack Tian的博客-CSDN博客
|