1. 为什么有servlet容器

前面讲到了servlet是一种规范,不能够独立提供服务,需要被部署到容器内部,由servlet容器来管理和运行servlet。servlet容器提供功能:

  • 生命周期管理:容器提供了servlet的运行环境,控制生命周期,负责加载、初始化和销毁servlet。
  • 通信支持:容器封装了http协议、端口监听、流创建读取等功能,可以是开发者更集中在servlet中service方法逻辑。
  • 多线程支持:容器在接收请求后自动创建多线程处理;

2. servlet容器分类介绍

先明白几个基本概念:

2.1 web服务器和web容器

用一张清晰的图片呈现:

windows 容器 有界面吗 server容器_web容器和web服务器

  • 服务器是提供计算服务的设备;
  • 容器屏蔽了服务器平台的复杂性,为部署在容器内的应用程序提供运行环境;

所以容器是位于应用程序/组件和服务器平台之间的接口集合,使得应用程序/组件可以方便部署到服务器上运行。

  • WEB容器:可以部署多个WEB应用程序的环境。
  • WEB服务器:一般指网站服务器,可以向浏览器等WEB客户端提供文档浏览、数据文件下载等WEB服务。

2.2 web 容器和servlet容器

windows 容器 有界面吗 server容器_web容器和web服务器_02

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

  • WebSphere 是 IBM 的集成软件平台

Resin

  • Resin是Caucho公司的产品,是一个非常流行的支持Servlet和JSP的服务器,速度非常快。
  • Resin本身包含了一个支持HTML的Web服务器,这使它不仅可以显示动态内容,而且显示静态内容的能力也毫不逊色,因此许多网站都是使用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文件;

顶层结构:

windows 容器 有界面吗 server容器_tomcat详解_03

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子容器;

windows 容器 有界面吗 server容器_tomcat调优_04

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连接器)有bionioapr三种运行模式。

模式

默认运行版本

处理方式

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类加载器

windows 容器 有界面吗 server容器_web容器和web服务器_05

  • 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 连接器线程池调优

windows 容器 有界面吗 server容器_web容器和web服务器_06

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"。