ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Tomcat server.xml配置详解:从端口到线程池的生产实践

Tomcat server.xml配置详解:从端口到线程池的生产实践 1. server.xml在Tomcat里的真实分量从端口冲突谈起1.1 一次启动失败让我开始认真读这份文件很多年以前我第一次在公司服务器上部署Java Web项目照着网上的教程改了Tomcat的端口结果Tomcat启动直接失败错误提示Address already in use: JVM_Bind。我第一反应是端口被占了检查了半个多小时最后才发现罪魁祸首竟然是server.xml里写了两份Connector配置8080和8009都被我改成了同一个端口值。那次事故之后我养成了一个习惯动server.xml之前先备份改完一定逐行检查标签嵌套和端口冲突。server.xml位于Tomcat安装目录下的conf文件夹里是整个Tomcat容器组装的总装图纸。它不负责业务逻辑却决定了Tomcat用什么端口监听、通过哪种协议对外服务、把请求交给哪个应用、一台机器能跑几个站点。你可以把它理解成房子的水电管线图——平时看不见一旦装修出问题全屋都跟着遭殃。很多新手对这份文件的认知停留在改端口就要找8080但实际生产环境里虚拟主机、HTTPS、AJP协议、线程池、Context映射全都堆在这一个文件里绕不开。1.2 加载机制一份XML如何变成运行中的TomcatTomcat启动时Bootstrap类会引导Catalina启动Catalina再通过DigesterApache Commons的XML解析工具把server.xml解析成一组相互关联的Java对象StandardServer、StandardService、StandardEngine、StandardHost、StandardContext等。你看到的 标签对应一个Server对象 标签对应一个Connector对象这些对象按层级组合起来就构成了Tomcat处理HTTP请求的完整链路。这里有个关键点server.xml只在启动阶段被读取一次。所以修改它不会像改Spring配置那样自动热加载必须重启Tomcat才生效。不少人改了端口发现没变化就是因为只改了文件没重启或者重启的时候把旧的Tomcat进程杀掉了、新进程根本没起来。这个配置不生效的坑后面我会单独讲排查方法。还有一个容易被忽略的细节server.xml结构非常严格标签嵌套顺序错了也会导致启动失败。比如 必须嵌在 里面 必须嵌在 里面写反了Tomcat会直接报SEVERE级别错误然后退出启动流程。这也是tomcat闪退里相当常见的一类原因。1.3 一份最小可用配置长什么样为了让你对全局有个直观印象我先贴一份简化到极致但能正常启动的server.xml骨架?xml version1.0 encodingUTF-8? Server port8005 shutdownSHUTDOWN Service nameCatalina Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443/ Engine nameCatalina defaultHostlocalhost Host namelocalhost appBasewebapps unpackWARstrue autoDeploytrue/ /Engine /Service /Server这份配置的职责链路是Server是总容器Service把Connector和Engine绑在一起Connector监听8080接收HTTP请求Engine根据Host头发给HostHost再扫描appBase目录里的WAR包或应用目录完成部署。把这条链路刻在脑子里后面每一节往上加东西都不会乱。2. 骨架三层分解Server、Service、Engine各管什么2.1 Server整个Tomcat进程的总闸先讲最外层。一份server.xml只允许存在一个 根元素它代表整个Tomcat运行实例。你最常见的配置是这样Server port8005 shutdownSHUTDOWN Listener classNameorg.apache.catalina.startup.VersionLoggerListener / ... /ServerServer上最值得关注的属性是port和shutdown这两个配合构成了Tomcat的关机广播通道。Tomcat运行期间会监听8005端口只要收到内容为SHUTDOWN的字符串就执行关闭流程。这是一把双刃剑本地调试很方便但如果服务器把这个端口暴露到公网任何人往8005端口发一个SHUTDOWN就能把服务打停。生产环境我的建议是把端口改成一个不常见的数字把SHUTDOWN字符串也改掉如果条件不允许改端口至少要在防火墙层面把8005限制为本机回环地址访问。还有一个实战中容易忽略的点Server节点下面那些Listener比如VersionLoggerListener、AprLifecycleListener它们是Tomcat启动过程中的钩子。如果你从网上下载了精简版server.xml做整体替换一定不要把默认的Listener全部删掉否则各种奇怪问题会冒出来。我曾经见过有人为了精简配置把AprLifecycleListener删了结果在部分Linux机器上SSL功能直接异常查了很久才找到原因。2.2 Service一个容器里能并行跑几条业务线节点夹在Server和Engine之间它起的是命名分组的作用。一个 可以包含多个 每个Service可以有自己独立的Connector和Engine组合。打个比方Server相当于一个工厂Service是工厂里的生产线——8080端口和8009端口各接一条线互不干扰。默认配置里有nameCatalina的Service有的版本还会带一个nameAJP的Service。比如Service nameAJP Connector port8009 protocolAJP/1.3 / Engine nameAJPEngine defaultHostlocalhost Host namelocalhost appBasewebapps / /Engine /Service为什么要拆成多个Service最常见的场景是一个Tomcat同时对外提供HTTP和AJP两种服务而它们的线程池策略、引擎路由可以完全独立。如果你只是在前端挂了Nginx做反向代理AJP这个Service大多数情况是用不到的直接注释掉就好。少一个监听端口就少一份暴露面这个思路对公网服务器尤为重要。2.3 Engine请求入口路由器在整个链路中扮演路由中枢。所有进入Service的请求最终都会交给Engine由Engine根据请求的Host头部信息匹配到具体的 。Engine上最核心的属性是defaultHost和name。defaultHost的意思是当你发起的请求匹配不到任何虚拟主机时默认落到哪台主机上。这也就是为什么server.xml里defaultHostlocalhost而后面Host的name也是localhost两者必须对得上。Engine还有一个属性jvmRoute这个属性在集群负载均衡实践中很有用。当你用Nginx给多个Tomcat做负载均衡时jvmRoute会作为标识追加在Session ID后面形如JSESSIONIDxxx.jvm1负载均衡器据此把带有会话粘性的请求转发到原始节点。如果没配jvmRouteSession跨节点复制后用户可能被踢来踢去登录状态说丢就丢。配置方式就是在 里加上jvmRoute每个节点的值不能重复。3. Connector连接器协议、端口、线程模型一次讲透3.1 HTTP/1.1连接器8080端口背后的参数怎么读Connector是整个server.xml里信息量最大的节点。先拆最常见的HTTP连接器Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 maxThreads200 minSpareThreads10 acceptCount100 maxConnections8192 URIEncodingUTF-8 /很多人把这些参数当成照抄模板其实这些参数背后是一套完整的连接排队模型connectionTimeout接收请求后等待请求行的最大时间毫秒。如果客户端建立了TCP连接但迟迟不发HTTP请求超过该时间Tomcat会直接断开避免空连接占满线程。acceptCount操作系统层面TCP连接等待队列的长度。队列满了之后新的TCP连接会被内核直接拒绝客户端表现就是Connection refused。maxConnectionsTomcat同时接受的最大TCP连接数。当连接数超过maxConnections时多余的连接进入acceptCount队列等待。maxThreads真正处理请求的执行线程数。连接被接受了、请求也被读取了最终要分给线程池里的线程去执行Servlet。这三个参数从外到内构成漏斗maxConnections是入口闸门acceptCount是闸门前排队空间maxThreads是闸门后的处理能力。它们不是越大越好需要根据机器的CPU核心数、内存和业务特性去配后面第5章我具体讲计算方法。3.2 protocol属性的演变NIO还是NIO2protocol属性是新手最爱的抄错现场。早期的Tomcat配置里protocolHTTP/1.1就是纯BIO阻塞IO一个连接占用一个线程并发大了线程数一路飙到几百上千CPU全耗在线程切换上。Tomcat 8.5之后BIO被彻底移除protocolHTTP/1.1会自动在NIO和NIO2之间选一个。NIO非阻塞IO模式下一个线程可以同时管理大量连接的IO事件真正处理业务时才占用线程这就是为什么现在Tomcat默认maxThreads只有200却可以支撑数千并发连接的原因。如果你希望显式指定某种模型可以这样写Connector port8080 protocolorg.apache.coyote.http11.Http11Nio2Protocol ... /生产环境我一般用NIO就够了。NIO2在多数测试里提升并不明显反而对某些操作系统版本有兼容性问题。不要为了炫技去强制NIO2稳定的方案往往是最简单的。3.3 AJP与HTTPS特殊协议怎么配才安全AJP协议是Tomcat跟Apache httpd协作的专用协议二进制格式性能确实好。但AJP历史上出过严重安全事件CVE-2020-1938也就是Ghostcat漏洞根本原因就是AJP连接器暴露在不可信网络上且缺少校验。所以现在配置AJP连接器必须加secret属性Connector port8009 protocolAJP/1.3 secretRequiredtrue secret改成你自己的强随机字符串 /如果没有前端Apache配合我的建议很明确直接把8009这段Connector连同AJP Service一起注释掉。多一个端口就多一份被扫描攻击的风险尤其公网服务器。HTTPS连接器稍微复杂一点它不只认port属性还要配SSLHostConfig证书和密钥Connector port8443 protocolHTTP/1.1 SSLEnabledtrue maxThreads150 SSLHostConfig Certificate certificateKeystoreFileconf/keystore.p12 certificateKeystorePasswordchangeit typePKCS12 / /SSLHostConfig /Connector关键点Tomcat 8.5推荐用PKCS12格式密钥库而不是JKS。证书链要放在同一个密钥库里并保证别名正确否则启动时虽然不报错浏览器访问却会提示证书链不完整。我遇到过有同事把根证书和服务器证书分开放在两个文件里Tomcat只加载了其中一个结果手机端访问一直报SSL错误折腾了一下午。4. Host虚拟主机与Context应用上下文一台机器跑多站点4.1 Host从localhost到多域名默认情况下 会接管Tomcat的webapps目录解压后的WAR包自动成为应用。如果你想一台服务器上跑多个域名办法是在 里加多个 Engine nameCatalina defaultHostlocalhost Host namelocalhost appBasewebapps / Host namewww.example.com appBasesite2apps / /Engine匹配逻辑是这样的客户端请求的Host头会跟Host的name字段比较匹配到哪个就进入哪个Host处理都匹配不到就走defaultHost。默认情况下每个Host会扫描自己的appBase目录把发现的WAR包或应用目录作为Context加载。这就意味着你完全可以把不同业务拆到不同Host每个Host用不同的appBase目录隔离部署。实际部署时还有个小坑Host的appBase目录如果不存在Tomcat启动时会自动创建但如果目录权限不对比如目录属于root而Tomcat用普通用户运行部署WAR包的时候会报权限错误看起来像应用没部署上去。遇到这种问题先ls -l看一眼目录属主别急着改server.xml。4.2 Context给应用绑定URL路径Context是Tomcat里表示一个Web应用的元素它决定了一个应用通过什么路径访问、从哪里加载资源。最常见的两种配置方式第一种直接写在server.xml的 里Host namelocalhost appBasewebapps Context path/order docBaseorder-server reloadablefalse / /Host第二种用独立xml文件放在conf/Catalina/localhost/目录下文件名就是访问路径。比如写一个order.xml里面配置docBase指向应用实际目录效果等同上面path/order的配置。这种方式我强烈推荐因为它不需要重启Tomcat就能被扫描到而且把应用级配置和全局配置分开了出问题时排查面小得多。docBase是应用实际资源的路径可以是绝对路径比如docBase/data/apps/order-server也可以相对于appBase的路径。注意如果你把WAR包直接丢进appBaseTomcat会自动解压并部署此时不需要写Context但如果你把应用放在appBase之外的目录就必须用Context显式声明docBase否则Tomcat根本不知道有这号应用。4.3 404源服务器未能找到……到底哪里错了热搜里有一条很典型的报错idea tomcat 描述 源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的。这其实是404页面的中文翻译看起来吓人本质就是Tomcat没有找到对应URL映射的资源。我排查这个报错的固定顺序是确认请求URL里的context path是否与应用实际路径一致。这是最大的坑你在IDEA里部署应用叫task-web访问写成了localhost:8080/task或者忘了加路径必然404。确认应用是否真的部署成功。打开Tomcat的webapps目录看有没有对应的文件夹或WAR包如果用的是IDEA的Smart Tomcat还要看Deployment里Artifact配置对不对。确认是前端还是后端路由问题。如果静态资源能访问但API接口404多半是应用自己的URL映射错了如果所有路径都404才是Tomcat层的问题。大多数Tomcat 404都是应用部署路径或URL拼写问题server.xml其实是背锅的。真正的server.xml导致的404往往是Context的path配置和docBase指向目录不存在或者应用根本没被加载。4.4 Alias与reloadable两个容易被忽略的Host属性上还有alias和reloadable值得一说。alias用来给同一个虚拟主机加别名比如主域名example.com和www.example.com指向同一个应用Host nameexample.com appBasewebapps Aliaswww.example.com/Alias /Host没有alias的话你用www域名访问时请求头里Host的内容是www.example.com会被Tomcat当作另一个站点处理匹配不到就落到defaultHost表现就是配置了虚拟主机但访问还是进了默认应用。reloadable属性控制当WEB-INF/classes或lib目录下文件发生变化时Tomcat是否自动重新加载应用。开发环境设为true确实方便但生产环境我建议设为false。自动加载的机制是增量扫描在高并发下触发reload会有一段不可用的空窗期而且这个操作吃内存、容易引发Full GC。生产环境改代码本来就是要走发布流程的让这个属性一直开着纯粹是给线上埋雷。5. Executor线程池与性能参数别盲目照抄默认值5.1 Executor把线程池单独拎出来管理线程池参数直接写在Connector上也能生效但如果一个Service里挂了多个Connector比如HTTP和HTTPS每个连接器各配一份线程池参数管理起来就很散。Tomcat提供了 标签让你可以定义一个共享线程池然后Connector用executor属性引用它Executor namewebThreadPool namePrefixweb-exec- maxThreads200 minSpareThreads20 maxIdleTime60000 / Connector port8080 protocolHTTP/1.1 executorwebThreadPool ... /这样做的最大好处是统一管理HTTP和HTTPS共享线程池避免两个连接器各自起线程导致总线程数翻倍。namePrefix会在线程栈和jstack输出里显示成web-exec-10之类的名字压测定位线程问题时一目了然比默认的http-nio-8080-exec-xx更容易串台。5.2 maxThreads、maxConnections、acceptCount怎么定很多人在网上搜到maxThreads800maxConnections10000然后就直接复制。我不建议这么做参数必须结合机器和业务算。线程池的合理上限一个常用参考思路是分场景CPU密集任务大量计算、加解密、压缩maxThreads建议不超过CPU核心数的1到2倍。IO密集任务访问数据库、调用外部HTTP接口、读写文件maxThreads可以设得高一些因为线程大量时间在等待IO没有真占CPU。粗略参考CPU核心数乘以10到20再结合压测结果微调。那Tomcat默认的200够不够如果每个请求处理时间在50ms左右200个线程理论上每秒能消化4000个请求200 / 0.05 4000一般中小项目绰绰有余。如果平均处理时间到了500ms200个线程每秒只能处理400个请求这个时候增加线程数确实能提升吞吐但线程多了上下文切换开销也会变大可能加到400反而效果不明显甚至回落。所以正确的做法不是抄数而是自己压测用JMeter或wrk压不同线程数下的吞吐曲线找到拐点。我没法给出一个放之四海而皆准的数字但可以给出一个合理起步配置再按压测结果调整参数起步建议说明minSpareThreads10~20空闲时保持存活的最小线程数避免请求来了临时建线程maxThreads200~400按CPU核数与业务耗时调整压测找拐点acceptCount100等待队列长度别超过maxConnections的几分之一maxConnections4096~8192跟进程文件描述符上限联动注意ulimit限制connectionTimeout20000一般不需要动内网场景可以调低Linux下还要注意maxConnections调得再高如果进程的nofile文件描述符上限很低TCP连接同样会失败。用ulimit -n查看生产环境至少调到65535。这是很多人容易漏掉的环节——server.xml参数没问题其实是操作系统参数卡住了。5.3 线程池监控改完成功率判断配置改完了怎么知道有没有效果我建议在Tomcat的Manager App或者JMX指标里看两个数据当前线程数currentThreadCount和忙碌线程数currentThreadsBusy。如果忙碌线程长期顶着maxThreads走说明线程池确实不够可以逐步上调并重新压测。如果忙碌线程大多数时间只有二三十个那就不要动maxThreads问题多半出在数据库SQL慢、远程调用超时这些业务环节加线程池只是饮鸩止渴。我还见过一种情况maxThreads调到2000之后接口响应时间没有变好垃圾回收反而频繁了。原因就是线程数太多导致内存中保存的请求上下文对象变多堆压力增大。所以线程池不是越大越好监控数据会告诉你真实答案。6. server.xml改完之后的那些坑闪退、乱码、4046.1 启动闪退先看日志别乱猜tomcat闪退这个问题常年上热搜。我的建议只有一条闪退之后立刻打开Tomcat的logs目录看catalina.outLinux或运行窗口的日志输出。Tomcat启动失败的根因几乎都会写在这里。最常见的server.xml相关闪退原因有这么几类标签没有闭合或嵌套顺序错误比如把 放在了错误层级Digester解析时会直接抛异常。端口被占用检查是不是有另一个Tomcat实例或者别的进程占了同一个端口我在一台测试机上同时跑多个Tomcat时经常遇到。shutdown端口冲突Tomcat的8005端口被别的实例占用同样会启动失败而且这个错误很容易被误判成正常的8080端口问题。引用了不存在的目录或文件比如Host的appBase指向一个Tomcat没有权限的目录或者在SSL配置里写了错误的证书路径。排查时记住一个口诀先看日志再改配置最后想是否跟环境有关。不要看到一个报错就百度一段配置乱改那样会把问题越滚越大。Tomcat 7还提供了一个很好的自查工具在bin目录下执行./catalina.sh configtest它会只校验配置不启动服务快速发现语法和端口冲突一类的基础错误改完server.xml先跑一遍再重启能省很多事。6.2 乱码问题URIEncoding背了几口锅关于乱码我先给结论Tomcat 8.0之后URIEncoding的默认值已经是UTF-8所以你很少需要在server.xml里额外写URIEncodingUTF-8。真正影响Tomcat下中文乱码的几个环节是请求参数乱码GET请求的URL参数经过URI处理POST请求的body编码取决于表单页面和Servlet的request.setCharacterEncoding跟server.xml关系不大。控制台日志乱码这通常是JVM默认编码跟系统不一致造成的在catalina.sh或者setenv.sh里加上-Dfile.encodingUTF-8再配合日志组件的编码就能解决。JSP页面输出乱码那是pageEncoding和response头的问题属于应用层别去改server.xml。早期Tomcat 7及更低版本默认URIEncoding是ISO-8859-1遇到中文参数会裂开所以老博客里大量出现在server.xml里加URIEncodingUTF-8的建议。现在的版本不需要这行了但加上也无害。我自己的习惯是写上它因为团队里可能有人把代码部署到老版本Tomcat多一行显式声明可以少踩一个历史坑。6.3 改了配置不生效进程、缓存、权限三个维度还有一个经常上热搜的现象是tomcat启动出现各种奇怪问题其中一类是改了server.xml之后Tomcat还是老样子。我通常按三个维度排查。首先是进程维度。确认你重启的到底是哪个Tomcat。生产机上可能有多个Tomcat安装目录systemd服务、Docker容器都可能是某个隐藏的Tomcat在跑。用ps -ef | grep java看启动命令里catalina.home参数指向哪里别在A目录改了配置实际跑的是B目录。其次是缓存维度。如果你用IDE比如IDEA内嵌方式运行Tomcat它可能复制了一份配置到target目录这时你改的是本地Tomcat的conf/server.xml而实际跑的是项目里打包生成的配置。检查运行输出里启动参数指向的catalina.base路径以那个路径下的server.xml为准。最后是权限维度。有些公司用统一的配置中心或发布平台下发server.xml直接改服务器文件会被下一次发布覆盖。遇到这种情况别硬改顺着部署流程找出真正的配置源从源头修改才是治本的办法。6.4 一个实战排查案例的完整链路讲一个最近帮同事排查的例子某个内部系统从测试环境迁移到新服务器Tomcat启动正常但访问任何应用都返回404同时日志里出现Deploying web application archive之后紧接着Deploying web application directory却没有任何报错。我当时没有直接改server.xml而是按顺序做了四步排查确认端口能通curl -I localhost:8080返回TCP连接成功但404。检查webapps目录发现WAR包确实存在解压后的目录也存在但目录内WEB-INF/classes是空的。检查部署日志catalina.日期.log里有一条Unexpected error deploying的疑似记录但catalina.out里没有。最后发现是解压WAR包的进程在打包时使用了Linux下的通配符把classes目录下的文件打丢了跟server.xml毫无关系。这个案例说明一个道理server.xml虽然是Tomcat配置的核心但很多现象是应用打包、文件权限、目录结构引起的。遇到问题先扩大排查范围不要动不动就怀疑配置文件更不要反复重启Tomcat碰运气。我自己在部署Java Web应用这几年最大的体会是server.xml值得被认真对待但它也是最容易改出问题的文件。每次动它之前我习惯先备份一份带时间戳的副本改完先跑一下configtest校验再重启服务。线上被报错逼急了也不要慌多看一眼日志大多数问题都有明确的提示。如果你现在正准备改端口、加虚拟主机或者配HTTPS先把这篇文章里的层级链路和参数模型理清至少能少踩一半的坑。
返回列表