本文共 17972 字,大约阅读时间需要 59 分钟。
下图是tomcat的一个整体架构,图可以与server.xml进行对比学习,标签都有关联,每个标签都对应一个类,可以尝试去查找一下源码,标签上的字段对应get与set。标签的详细介绍可查看
Container 是个集合 (结合图可以更加深刻理解,图形对于集合是层叠的一个图形)
大部分应用容器都是基于tomcat实现的,比如jboss、weblogic等都参考了tomcat
catalina.policy : Tomcat 安全策略文件,控制 JVM 相关权限,具体可以参考java.security.Permission
catalina.properties : Tomcat Catalina 行为控制配置文件,加载lib目录的jar包中的类,比如 Common ClassLoader logging.properties : Tomcat 日志配置文件,JDK Logging server.xml : Tomcat Server 配置文件Tomcat 存放公用类库
ecj-*.jar : Eclipse Java 编译器
jasper.jar : JSP 编译器localhost.${date}.log : 当 Tomcat 应用起不来的时候,多看该文件,比如:类冲突
catalina.${date}.log : 控制台输出(System.out 输出控制台的同时输出到文件)
部署 Web 应用
方法一:放置在 webapps目录 直接拖过去方法二: 修改 confi/server.xml
添加Context 元素:熟悉配置元素可以参考org.apache.catalina.core.StandardContext setter 方法
该方式不支持动态部署,建议考虑在生产环境使用。方法三:独立 context xml 配置文件
首先注意 conf\Catalina\localhost 独立 context XML 配置文件路径:${TOMCAT_HOME}/conf/Catalina/localhost + ${ContextPath} .xml 注意:该方式可以实现热部署,因此建议在开发环境使用实现类:org.apache.catalina.connector.Connector
tomcat8实现了NIO,server.xml的Connector可以配置
BIO与NIO的对比(详细查看官方文档)
Java Blocking Connector Java Non Blocking Connector APR/native Connector BIO NIO APR Classname Http11Protocol Http11NioProtocol Http11AprProtocol Tomcat Version 3.x onwards 6.x onwards 5.5.x onwards Support Polling NO YES YES Polling Size N/A maxConnections maxConnections Read Request Headers Blocking Non Blocking Blocking Read Request Body Blocking Blocking Blocking Write Response Blocking Blocking Blocking Wait for next Request Blocking Non Blocking Non Blocking SSL Support Java SSL Java SSL OpenSSL SSL Handshake Blocking Non blocking Blocking Max Connections maxConnections maxConnections maxConnections
查看tomcat文档可知容器的默认编码
URIEncoding | This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, ISO-8859-1 will be used. |
---|
注意conf/server.xml 文件中的一段注释:
org.apache.catalina.Executor:
public interface Executor extends java.util.concurrent.Executor, Lifecycle { public String getName(); /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thread, or in the calling * thread, at the discretion of the Executor implementation. * If no threads are available, it will be added to the work queue. * If the work queue is full, the system will wait for the specified * time until it throws a RejectedExecutionException * * @param command the runnable task * @throws java.util.concurrent.RejectedExecutionException if this task * cannot be accepted for execution - the queue is full * @throws NullPointerException if command or unit is null */ void execute(Runnable command, long timeout, TimeUnit unit);}
tomcat线程池的标准实现:org.apache.catalina.core.StandardThreadExecutor ->
追踪源码可知是将连接处理交付给 Java 标准线程池:org.apache.tomcat.util.threads.ThreadPoolExecutor。... ...
Context initCtx = new InitialContext();//类似SpringContextContext envCtx = (Context) initCtx.lookup("java:comp/env");//类似spring的依赖查找Session session = (Session) envCtx.lookup("mail/Session");Message message = new MimeMessage(session);message.setFrom(new InternetAddress(request.getParameter("from")));InternetAddress to[] = new InternetAddress[1];to[0] = new InternetAddress(request.getParameter("to"));message.setRecipients(Message.RecipientType.TO, to);message.setSubject(request.getParameter("subject"));message.setContent(request.getParameter("content"), "text/plain");Transport.send(message);
Servlet 3.0 + API 实现自动装配,核心API是 ServletContainerInitializer
传统的 Web 应用,将 webapp 部署到 Servlet 容器中。
嵌入式 Web 应用,灵活部署,任意指定位置(或者通过复杂的条件判断)Tomcat 7 是 Servlet 3.0 的实现,ServletContainerInitializer
HttpServletRequest
、HttpServletResponse
NIO 并非一定能够提高性能,比如请求数据量较大,NIO 性能比 BIO 还要差
NIO 多工,读、写,同步非阻塞
java -jar
或者 jar
读取 .jar文件里面的META-INF/MANIFEST.MF
,其中属性 Main-Class
就是引导类所在。
参考 JDK API :
java.util.jar.Manifest
org.apache.tomcat.maven tomcat7-maven-plugin 2.1
查看 META-INF/MANIFEST.MF
Manifest-Version: 1.0Main-Class: org.apache.tomcat.maven.runner.Tomcat7RunnerCli
得出 Tomcat 7 可执行 jar 引导类是org.apache.tomcat.maven.runner.Tomcat7RunnerCli
org.apache.tomcat.maven.runner.Tomcat7Runner
查看代码可知此处就是tomcat的api调用与配置代码了(我们可以参考该代码依葫芦画瓢)
tomcat = new Tomcat(){ public Context addWebapp( Host host, String url, String name, String path ){ Context ctx = new StandardContext(); ctx.setName( name ); ctx.setPath( url ); ctx.setDocBase( path ); ContextConfig ctxCfg = new ContextConfig(); ctx.addLifecycleListener( ctxCfg ); ctxCfg.setDefaultWebXml( new File( extractDirectory, "conf/web.xml" ).getAbsolutePath() ); if ( host == null ) { getHost().addChild( ctx ); } else { host.addChild( ctx ); } return ctx;}};if ( this.enableNaming() ){ System.setProperty( "catalina.useNaming", "true" );tomcat.enableNaming();}tomcat.getHost().setAppBase( new File( extractDirectory, "webapps" ).getAbsolutePath() );String connectorHttpProtocol = runtimeProperties.getProperty( HTTP_PROTOCOL_KEY );if ( httpProtocol != null && httpProtocol.trim().length() > 0 ){ connectorHttpProtocol = httpProtocol;}debugMessage( "use connectorHttpProtocol:" + connectorHttpProtocol );if ( httpPort > 0 ){ Connector connector = new Connector( connectorHttpProtocol );connector.setPort( httpPort );if ( httpsPort > 0 ){ connector.setRedirectPort( httpsPort );}connector.setURIEncoding( uriEncoding );tomcat.getService().addConnector( connector );tomcat.setConnector( connector );}// add a default acces log valveAccessLogValve alv = new AccessLogValve();alv.setDirectory( new File( extractDirectory, "logs" ).getAbsolutePath() );alv.setPattern( runtimeProperties.getProperty( Tomcat7Runner.ACCESS_LOG_VALVE_FORMAT_KEY ) );tomcat.getHost().getPipeline().addValve( alv );// create https connectorif ( httpsPort > 0 ){ Connector httpsConnector = new Connector( connectorHttpProtocol );httpsConnector.setPort( httpsPort );httpsConnector.setSecure( true );httpsConnector.setProperty( "SSLEnabled", "true" );httpsConnector.setProperty( "sslProtocol", "TLS" );httpsConnector.setURIEncoding( uriEncoding );String keystoreFile = System.getProperty( "javax.net.ssl.keyStore" );String keystorePass = System.getProperty( "javax.net.ssl.keyStorePassword" );String keystoreType = System.getProperty( "javax.net.ssl.keyStoreType", "jks" );if ( keystoreFile != null ){ httpsConnector.setAttribute( "keystoreFile", keystoreFile );}if ( keystorePass != null ){ httpsConnector.setAttribute( "keystorePass", keystorePass );}httpsConnector.setAttribute( "keystoreType", keystoreType );String truststoreFile = System.getProperty( "javax.net.ssl.trustStore" );String truststorePass = System.getProperty( "javax.net.ssl.trustStorePassword" );String truststoreType = System.getProperty( "javax.net.ssl.trustStoreType", "jks" );if ( truststoreFile != null ){ httpsConnector.setAttribute( "truststoreFile", truststoreFile );}if ( truststorePass != null ){ httpsConnector.setAttribute( "truststorePass", truststorePass );}httpsConnector.setAttribute( "truststoreType", truststoreType );httpsConnector.setAttribute( "clientAuth", clientAuth );httpsConnector.setAttribute( "keyAlias", keyAlias );tomcat.getService().addConnector( httpsConnector );if ( httpPort <= 0 ){ tomcat.setConnector( httpsConnector );}}// create ajp connectorif ( ajpPort > 0 ){ Connector ajpConnector = new Connector( "org.apache.coyote.ajp.AjpProtocol" );ajpConnector.setPort( ajpPort );ajpConnector.setURIEncoding( uriEncoding );tomcat.getService().addConnector( ajpConnector );}// add webappsfor ( Map.Entryentry : this.webappWarPerContext.entrySet() ){ String baseDir = null;Context context = null;if ( entry.getKey().equals( "/" ) ){ baseDir = new File( extractDirectory, "webapps/ROOT.war" ).getAbsolutePath(); context = tomcat.addWebapp( "", baseDir );}else{ baseDir = new File( extractDirectory, "webapps/" + entry.getValue() ).getAbsolutePath(); context = tomcat.addWebapp( entry.getKey(), baseDir );}URL contextFileUrl = getContextXml( baseDir );if ( contextFileUrl != null ){ context.setConfigFile( contextFileUrl );}}if ( codeSourceWar != null ){ String baseDir = new File( extractDirectory, "webapps/" + codeSourceWar.getName() ).getAbsolutePath();Context context = tomcat.addWebapp( codeSourceContextPath, baseDir );URL contextFileUrl = getContextXml( baseDir );if ( contextFileUrl != null ){ context.setConfigFile( contextFileUrl );}}tomcat.start();Runtime.getRuntime().addShutdownHook( new TomcatShutdownHook() );}waitIndefinitely();//阻塞主线程
按照tomcat架构图来编程,tomcat的架构关键元素如下
Embedded/Tomcat Service Engine Host Connector Contextclasses 绝对路径:E:\Downloads\tomcat\target\classes
String classesPath = System.getProperty("user.dir") + File.separator + "target" + File.separator + "classes";
org.apache.catalina.startup.Tomcat
Maven 坐标:org.apache.tomcat.embed:tomcat-embed-core:7.0.37// 设置 HostHost host = tomcat.getHost();host.setName("localhost");host.setAppBase("webapps");
Classpath 读取资源:配置、类文件
conf/web.xml 作为配置文件,并且放置 Classpath 目录下(绝对路径)// 添加 DemoServlet 到 Tomcat 容器Wrapper wrapper = tomcat.addServlet(contextPath, "DemoServlet", new DemoServlet());wrapper.addMapping("/demo");
main/resource/conf/web.xml 复制tomcat的web.xml
main/webapp/index.jsp 测试jsp DemoServlet.java 测试servlet EmbeddedTomcatServer.java tomcat启动类public static void main(String[] args) throws Exception { // classes 目录绝对路径 // E:\Downloads\tomcat\target\classes String classesPath = System.getProperty("user.dir") + File.separator + "target" + File.separator + "classes"; System.out.println(classesPath); Tomcat tomcat = new Tomcat(); // 设置端口 12345 tomcat.setPort(12345); // 设置 Host Host host = tomcat.getHost(); host.setName("localhost"); host.setAppBase("webapps"); // 设置 Context // E:\Downloads\tomcat\src\main\webapp String webapp = System.getProperty("user.dir") + File.separator + "src" + File.separator + "main" + File.separator + "webapp"; String contextPath = "/"; // 设置 webapp 绝对路径到 Context,作为它的 docBase Context context = tomcat.addWebapp(contextPath, webapp); if (context instanceof StandardContext) { StandardContext standardContext = (StandardContext) context; // 设置默认的web.xml文件到 Context standardContext.setDefaultWebXml(classesPath + File.separator + "conf/web.xml"); // 设置 Classpath 到 Context // 添加 DemoServlet 到 Tomcat 容器 Wrapper wrapper = tomcat.addServlet(contextPath, "DemoServlet", new DemoServlet()); wrapper.addMapping("/demo"); } // 设置 Service Service service = tomcat.getService(); // 设置 Connector /** **/ Connector connector = new Connector(); connector.setPort(9090); connector.setURIEncoding("UTF-8"); connector.setProtocol("HTTP/1.1"); service.addConnector(connector); // 启动 Tomcat 服务器 tomcat.start(); // 强制 Tomcat Server 等待,避免 main 线程执行结束关闭 tomcat.getServer().await();}
EmbeddedServletContainerCustomizer
ConfigurableEmbeddedServletContainer EmbeddedServletContainer TomcatContextCustomizer TomcatConnectorCustomizer@Configurationpublic class TomcatConfiguration implements EmbeddedServletContainerCustomizer { @Override public void customize(ConfigurableEmbeddedServletContainer container) { System.err.println(container.getClass()); if (container instanceof TomcatEmbeddedServletContainerFactory) { TomcatEmbeddedServletContainerFactory factory= (TomcatEmbeddedServletContainerFactory) container;// Connector connector = new Connector();// connector.setPort(9090);// connector.setURIEncoding("UTF-8");// connector.setProtocol("HTTP/1.1");// factory.addAdditionalTomcatConnectors(connector); } }}
实现TomcatContextCustomizer
// 相当于 new TomcatContextCustomizer(){} factory.addContextCustomizers((context) -> { // Lambda if (context instanceof StandardContext) { StandardContext standardContext = (StandardContext) context; // standardContext.setDefaultWebXml(); // 设置 } });
实现 TomcatConnectorCustomizer
// 相当于 new TomcatConnectorCustomizer() {} factory.addConnectorCustomizers(connector -> { connector.setPort(12345); });
内嵌tomcat是不是比单独的tomcat不是在某方面具有一些优势?
嵌入式 Tomcat 或者嵌入式 Web 容器可以不依赖文件目录,比如在 Docker 场景使用方便。内置的tomcat 和 外部的tomcat 性能有多大差别?生产线上建议用哪个?
嵌入式 Tomcat 和 传统 Tomcat 性能可以说一样,现在非常多的生产环境 Spring Boot 嵌入式 - 嵌入式 TomcatSpring中pre实例化和pre初始化区别(刚刚有提到)
在 Spring 早起版本中,先有初始化生命周期 - org.springframework.beans.factory.config.BeanPostProcessor 后来 Spring 1.2 ,提供新的扩展接口(BeanPostProcessor): org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorTomcat 热部署原理能给讲下吗?
org.apache.catalina.Lifecycle 监控资源变化,Class 变化、配置变化了。 Tomcat 决定重启 Contextorg.apache.catalina.Context
Tomcat 自动加载
public void setReloadable(boolean reloadable)分析:它不需要静态资源,移除Tomcat 容器静态和动态
静态处理:DefaultServlet tomcat不需要处理静态资源文件,交给nginx
动态:JspServlet
移除 welcome-file-list
index.html index.htm index.jsp如果程序是REST JSON Content-Type 或者 MIME Type: application/json
移除 Session 设置
对于微服务/REST 应用,不需要 Session,因为不需要状态。移除 Valve
Valve 类似于 Filter 移除 AccessLogValve,可以通过 Nginx 的 Access Log 替代,Valve 实现都需要消耗 Java 应用的计算时间。分析:JspServlet 无法移除,了解 JspServlet 处理原理
1)Servlet 周期:
Servlet 或者 Filter 在一个容器中,是一般情况在一个 Web App 中是一个单例,不排除应用定义多个(servlet默认是单例,可以配置多个)。
2)JspServlet 相关的优化 ServletConfig 参数:
需要编译
development = false ,那么,这些 JSP 要如何编译。优化方法:
org.apache.sling jspc-maven-plugin 2.1.0
JSP -> 翻译.jsp 或者.jspx 文件成 .java -> 编译 .class
JspServlet 如果 development 参数为 true,它会自定检查文件是否修改,如果修改重新翻译,再编译(加载和执行)。言外之意,JspServlet 开发模式可能会导致内存溢出。卸载 Class不及时会导致 Perm 区域不够。
ParentClassLoader -> 1.class 2.class 3.class
ChildClassLoader -> 4.class , 5.class ChildClassLoader load 1 - 5 .class如果1.class 卸载,需要将 ParentClassLoader 设置为 null,当 ClassLoader 被 GC 后,1-3 class 全部会被卸载。
3)
conf/web.xml 作为 Servlet 应用的默认web.xml,实际上,应用程序存在两份web.xml,其中包括Tomcat conf/web.xml 和 应用的web.xml,最终将两者合并。context.xml
server.xml
通过程序来理解, 实际的Tomcat 接口:
/** * max number of threads */ protected int maxThreads = 200; /** * min number of threads */ protected int minSpareThreads = 25; public void setMinSpareThreads(int minSpareThreads) { this.minSpareThreads = minSpareThreads; if (executor != null) { executor.setCorePoolSize(minSpareThreads); } } public void setMaxThreads(int maxThreads) { this.maxThreads = maxThreads; if (executor != null) { executor.setMaximumPoolSize(maxThreads); } }
通过jsconsole可以动态调整线程数等
观察StandardThreadExecutor是否存在调整线程池数量的 API
评估一些参考
首先,评估整体的情况量,假设 100W QPS,有机器数量 100 台,每台支撑 1w QPS。
第二,进行压力测试,需要一些测试样本,JMeter 来实现,假设一次请求需要RT 10ms,1秒可以同时完成 100个请求。10000 / 100 = 100 线程。
第三,常规性压测,每次更新都测试一次,由于业务变更,会导致底层性能变化。
查看配置类 org.springframework.boot.autoconfigure.web.ServerProperties
Maven 依赖(因为server.jspServlet.registered需要该依赖,没有该依赖springboot会忽略该配置,即自动装配需条件满足)
org.apache.tomcat.embed tomcat-embed-jasper
application.properties
### 线程池大小server.tomcat.maxThreads = 99server.tomcat.minSpareThreads = 9## 取消 Tomcat AccessLogValveserver.tomcat.accesslog.enabled = false## 取消 JspServletserver.jspServlet.registered=false
转载地址:http://uvmxb.baihongyu.com/