境到WAR部署)
簡(jiǎn)介Apache Tomcat 7.0.108 是一款開源的 Java Servlet 容器主要用于運(yùn)行 JSP 與 Servlet 等 Java Web 應(yīng)用。這份 zip 壓縮包面向 Java Web 開發(fā)者、運(yùn)維人員及計(jì)算機(jī)專業(yè)學(xué)生幫助其快速搭建本地或測(cè)試環(huán)境的 Web 服務(wù)器完成項(xiàng)目部署與調(diào)試。包內(nèi)共 640 個(gè)文件約 10.38MB以 html 文檔、class 字節(jié)碼、java 源碼、jsp 頁(yè)面、gif 圖片、jar 依賴包、xml 配置、properties 屬性文件及 bat/sh 啟停腳本為主完整覆蓋 bin、conf、lib、webapps 等核心目錄便于理解 Tomcat 的目錄結(jié)構(gòu)與運(yùn)行機(jī)制。資源還包含 war 包、tld 標(biāo)簽庫(kù)、xsd 與 dtd 約束文件等可輔助讀者掌握 WAR 部署、Context 配置、日志查看與管理工具的使用。目前已有 633 人學(xué)習(xí)下載適合需要熟悉 Tomcat 7 部署流程、排查啟動(dòng)與訪問(wèn)問(wèn)題的開發(fā)者參考。1. 從一臺(tái)老項(xiàng)目的部署說(shuō)起為什么我還在裝 Tomcat 7.0.108上周幫一個(gè)朋友處理他公司遺留系統(tǒng)的部署問(wèn)題打開pom.xml一看javax.servlet版本還停留在 3.0JDK 編譯級(jí)別是 1.7。這種項(xiàng)目你上 Tomcat 9 或者 10啟動(dòng)直接給你拋NoClassDefFoundError或者ClassNotFoundException連日志都來(lái)不及打全就掛了。翻了一圈最穩(wěn)的落點(diǎn)就是 Tomcat 7.0.108——這是 7.x 分支最后一個(gè)發(fā)布版本修掉了之前幾個(gè)安全漏洞又沒(méi)引入 8.x 之后那些對(duì)老web.xml解析更嚴(yán)格的校驗(yàn)邏輯。所以這篇筆記不是講 Tomcat 怎么用而是講怎么把tomcat-7.0.108.zip這個(gè)包在一個(gè)干凈環(huán)境里裝起來(lái)、配好、跑通順便把我在這個(gè)版本上踩過(guò)的幾個(gè)坑攤開說(shuō)。適合誰(shuí)看手上維護(hù)著 JDK 7 或 JDK 8 早期版本、Servlet 3.0 規(guī)范、又不想動(dòng)代碼去適配新容器的開發(fā)者。如果你是新項(xiàng)目直接上 Tomcat 9 或 10別回頭折騰這個(gè)版本。2. 裝之前先把環(huán)境對(duì)清楚JDK 版本和目錄規(guī)劃2.1 Tomcat 7.0.108 對(duì) JDK 的硬性要求Tomcat 7 整個(gè)系列編譯時(shí)用的 JDK 是 1.6運(yùn)行時(shí)最低要求 JRE 1.6。但 7.0.108 這個(gè)版本我實(shí)測(cè)下來(lái)跑在 JDK 1.7 和 JDK 1.8 上都沒(méi)問(wèn)題JDK 1.6 反而因?yàn)橐恍┘用芴准膯?wèn)題在 HTTPS 場(chǎng)景下會(huì)報(bào)SSLHandshakeException。所以我的建議很直接用 JDK 1.8別用 1.6也別用 1.9 以上。為什么不用 JDK 9Tomcat 7 的啟動(dòng)腳本catalina.sh里有一堆對(duì)JAVA_OPTS和CATALINA_OPTS的解析邏輯在 JDK 9 的模塊化系統(tǒng)下會(huì)觸發(fā)Unrecognized VM option之類的報(bào)錯(cuò)。而且 JDK 11 之后移除了tools.jarTomcat 7 的 JSP 編譯依賴它你會(huì)在訪問(wèn) JSP 頁(yè)面時(shí)看到Unable to find a javac compiler。先確認(rèn)環(huán)境# 查看當(dāng)前 JDK 版本 java -version # 輸出示例 # java version 1.8.0_202 # Java(TM) SE Runtime Environment (build 1.8.0_202-b08) # Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode) # 確認(rèn) JAVA_HOME 是否設(shè)置 echo $JAVA_HOME # 如果為空需要手動(dòng)指定比如 export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64JAVA_HOME這個(gè)變量 Tomcat 的啟動(dòng)腳本會(huì)讀如果沒(méi)設(shè)或者指向了 JRE 而不是 JDKJSP 編譯功能直接廢掉。我一般會(huì)在/etc/profile.d/下單獨(dú)寫一個(gè)tomcat-env.sh把JAVA_HOME和CATALINA_HOME都固化進(jìn)去避免每次重啟機(jī)器都要手動(dòng) export。2.2 目錄結(jié)構(gòu)怎么擺才不給自己挖坑tomcat-7.0.108.zip解壓出來(lái)就是一個(gè)apache-tomcat-7.0.108目錄里面bin、conf、webapps、logs、temp、work六個(gè)核心目錄。我見過(guò)太多人直接解壓到/root/或者/home/xxx/下載/下面然后跑起來(lái)發(fā)現(xiàn)日志寫不進(jìn)去、臨時(shí)文件目錄權(quán)限不對(duì)。我的習(xí)慣是統(tǒng)一放在/opt/tomcat/下面用軟鏈接指向具體版本# 創(chuàng)建統(tǒng)一目錄 sudo mkdir -p /opt/tomcat # 解壓 zip 包到指定目錄 sudo unzip tomcat-7.0.108.zip -d /opt/tomcat/ # 建立軟鏈接方便后續(xù)版本切換 sudo ln -s /opt/tomcat/apache-tomcat-7.0.108 /opt/tomcat/current # 確認(rèn)目錄結(jié)構(gòu) ls -l /opt/tomcat/current/ # 應(yīng)該看到 bin conf lib logs temp webapps work 等目錄用軟鏈接的好處是以后升級(jí)版本只需要把新版本解壓到/opt/tomcat/下然后把current指向新目錄CATALINA_HOME環(huán)境變量不用改。另外logs和temp目錄建議單獨(dú)掛載或者至少確保運(yùn)行 Tomcat 的用戶有寫權(quán)限否則啟動(dòng)時(shí)會(huì)報(bào)java.io.FileNotFoundException: /opt/tomcat/current/logs/catalina.out (Permission denied)。提示如果你打算用 80 端口跑 TomcatLinux 下非 root 用戶無(wú)法綁定 1024 以下端口。要么用authbind授權(quán)要么在前面掛 Nginx 做反向代理。我一般選后者Tomcat 只監(jiān)聽 8080Nginx 轉(zhuǎn)發(fā) 80 和 443。3. 解壓、配置、啟動(dòng)把 zip 包變成能跑的服務(wù)3.1 解壓后的第一輪配置server.xml 改哪幾個(gè)地方conf/server.xml是 Tomcat 的核心配置文件7.0.108 這個(gè)版本的默認(rèn)配置里有幾個(gè)地方我每次都會(huì)改。第一個(gè)是Connector的URIEncoding。默認(rèn)沒(méi)寫在中文環(huán)境下 GET 請(qǐng)求參數(shù)會(huì)亂碼。改成 UTF-8!-- conf/server.xml 中找到 Connector 標(biāo)簽 -- Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 maxThreads300 minSpareThreads30 acceptCount500 /URIEncodingUTF-8解決 GET 請(qǐng)求中文亂碼maxThreads默認(rèn)是 200我調(diào)到 300 是因?yàn)槔享?xiàng)目里有些同步阻塞的 Servlet線程池太小會(huì)排隊(duì)acceptCount是等待隊(duì)列長(zhǎng)度默認(rèn) 100調(diào)到 500 防止高并發(fā)時(shí)直接拒絕連接。第二個(gè)是Host標(biāo)簽里的appBase和unpackWARs。默認(rèn)appBasewebappsunpackWARstrue這個(gè)組合沒(méi)問(wèn)題。但如果你把 WAR 包放在外部目錄需要加一個(gè)Context標(biāo)簽指定docBase。我一般不動(dòng)這個(gè)直接把 WAR 丟進(jìn)webapps讓它自動(dòng)解壓。第三個(gè)是AccessLogValve默認(rèn)是注釋掉的。生產(chǎn)環(huán)境建議打開方便排查請(qǐng)求問(wèn)題!-- 在 Host 標(biāo)簽內(nèi)取消注釋并修改 -- Valve classNameorg.apache.catalina.valves.AccessLogValve directorylogs prefixlocalhost_access_log. suffix.txt pattern%h %l %u %t quot;%rquot; %s %b %D /%D是請(qǐng)求處理時(shí)間單位毫秒排查慢請(qǐng)求時(shí)很有用。默認(rèn)的pattern里沒(méi)有這個(gè)我習(xí)慣加上。3.2 啟動(dòng)腳本和 JVM 參數(shù)怎么調(diào)Tomcat 7 的啟動(dòng)腳本在bin/catalina.sh但我不建議直接改這個(gè)文件因?yàn)榘姹旧?jí)時(shí)會(huì)被覆蓋。正確做法是在bin/下新建一個(gè)setenv.shTomcat 啟動(dòng)時(shí)會(huì)自動(dòng)讀取#!/bin/bash # /opt/tomcat/current/bin/setenv.sh # JVM 內(nèi)存參數(shù) export CATALINA_OPTS$CATALINA_OPTS -Xms512m -Xmx1024m export CATALINA_OPTS$CATALINA_OPTS -XX:PermSize128m -XX:MaxPermSize256m # 時(shí)區(qū)和編碼 export CATALINA_OPTS$CATALINA_OPTS -Duser.timezoneAsia/Shanghai export CATALINA_OPTS$CATALINA_OPTS -Dfile.encodingUTF-8 # 關(guān)閉 DNS 查詢加快啟動(dòng)速度 export CATALINA_OPTS$CATALINA_OPTS -Djava.net.preferIPv4Stacktrue注意PermSize和MaxPermSize這兩個(gè)參數(shù)在 JDK 8 里已經(jīng)被MetaspaceSize和MaxMetaspaceSize取代了。如果你用 JDK 8會(huì)看到Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize128m; support was removed in 8.0的警告不影響運(yùn)行但看著煩。換成# JDK 8 環(huán)境下用這兩個(gè) export CATALINA_OPTS$CATALINA_OPTS -XX:MetaspaceSize128m -XX:MaxMetaspaceSize256m-Xms和-Xmx設(shè)成一樣大可以避免堆動(dòng)態(tài)擴(kuò)容帶來(lái)的性能抖動(dòng)我一般設(shè)成物理內(nèi)存的一半但不超過(guò) 2G因?yàn)?Tomcat 7 在老項(xiàng)目里通常不會(huì)跑太大的堆。給setenv.sh加執(zhí)行權(quán)限chmod x /opt/tomcat/current/bin/setenv.sh然后啟動(dòng)# 啟動(dòng) Tomcat /opt/tomcat/current/bin/startup.sh # 查看啟動(dòng)日志 tail -f /opt/tomcat/current/logs/catalina.out看到Server startup in xxx ms就說(shuō)明啟動(dòng)成功了。如果卡在Starting Servlet Engine不動(dòng)大概率是某個(gè) webapp 的web.xml解析出問(wèn)題去logs/localhost.log里找具體是哪個(gè)應(yīng)用。3.3 把 Tomcat 注冊(cè)成 systemd 服務(wù)用startup.sh啟動(dòng)的 Tomcat 在終端關(guān)閉后可能會(huì)掛掉而且開機(jī)不會(huì)自啟。生產(chǎn)環(huán)境我一般注冊(cè)成 systemd 服務(wù)# /etc/systemd/system/tomcat.service [Unit] DescriptionApache Tomcat 7.0.108 Afternetwork.target [Service] Typeforking Usertomcat Grouptomcat EnvironmentJAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 EnvironmentCATALINA_HOME/opt/tomcat/current EnvironmentCATALINA_BASE/opt/tomcat/current ExecStart/opt/tomcat/current/bin/startup.sh ExecStop/opt/tomcat/current/bin/shutdown.sh Restarton-failure RestartSec10 [Install] WantedBymulti-user.target這里有幾個(gè)點(diǎn)要注意Typeforking是因?yàn)閟tartup.sh會(huì) fork 一個(gè)后臺(tái)進(jìn)程User和Group要提前創(chuàng)建并且把/opt/tomcat/目錄的屬主改成這個(gè)用戶Restarton-failure讓 Tomcat 崩潰后自動(dòng)拉起但如果是配置錯(cuò)誤導(dǎo)致啟動(dòng)失敗會(huì)無(wú)限重啟所以調(diào)試階段建議先注釋掉這行。# 創(chuàng)建 tomcat 用戶 sudo useradd -r -s /sbin/nologin tomcat sudo chown -R tomcat:tomcat /opt/tomcat/ # 重載 systemd 并啟動(dòng) sudo systemctl daemon-reload sudo systemctl start tomcat sudo systemctl enable tomcat # 查看狀態(tài) sudo systemctl status tomcat4. 避坑記錄Tomcat 7.0.108 上我踩過(guò)的五個(gè)坑4.1 坑一JSP 頁(yè)面報(bào) Unable to find a javac compiler現(xiàn)象訪問(wèn) JSP 頁(yè)面時(shí)返回 500日志里寫org.apache.jasper.JasperException: Unable to find a javac compiler。原因Tomcat 7 的 JSP 編譯依賴 JDK 的tools.jar如果JAVA_HOME指向的是 JRE 而不是 JDK或者 JDK 9 以上版本移除了tools.jar就會(huì)報(bào)這個(gè)錯(cuò)。解決確認(rèn)JAVA_HOME指向 JDK 安裝目錄且目錄下有l(wèi)ib/tools.jar。JDK 8 的路徑一般是/usr/lib/jvm/java-8-openjdk-amd64檢查ls $JAVA_HOME/lib/tools.jar是否存在。如果用的是 JDK 9要么降級(jí)到 JDK 8要么在setenv.sh里加-Djava.endorsed.dirs指向兼容包但后者很麻煩不推薦。4.2 坑二啟動(dòng)時(shí)報(bào) Address already in use現(xiàn)象startup.sh執(zhí)行后日志顯示java.net.BindException: Address already in use端口被占用。原因8080 端口被其他進(jìn)程占了可能是之前沒(méi)關(guān)干凈的 Tomcat 進(jìn)程也可能是其他 Web 服務(wù)。解決# 查看 8080 端口占用情況 sudo lsof -i :8080 # 或者 sudo netstat -tlnp | grep 8080 # 如果確認(rèn)是殘留的 Tomcat 進(jìn)程直接 kill sudo kill -9 PID如果不想殺進(jìn)程改server.xml里的Connector port換個(gè)端口比如 8081。但注意redirectPort也要同步改否則 HTTPS 跳轉(zhuǎn)會(huì)出問(wèn)題。4.3 坑三中文參數(shù)亂碼POST 和 GET 表現(xiàn)不一致現(xiàn)象POST 請(qǐng)求中文正常GET 請(qǐng)求中文亂碼或者反過(guò)來(lái)。原因Tomcat 7 默認(rèn)的URIEncoding是 ISO-8859-1GET 請(qǐng)求參數(shù)按這個(gè)編碼解析。POST 請(qǐng)求的編碼由web.xml里的CharacterEncodingFilter或者請(qǐng)求頭Content-Type決定。解決在server.xml的Connector里加URIEncodingUTF-8同時(shí)確保應(yīng)用的web.xml里配了CharacterEncodingFilterfilter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping兩邊都配了才能保證 GET 和 POST 都不亂碼。4.4 坑四WAR 包自動(dòng)解壓后舊文件沒(méi)被覆蓋現(xiàn)象更新 WAR 包后重啟 Tomcat發(fā)現(xiàn)代碼還是舊的。原因Tomcat 默認(rèn)的unpackWARstrue會(huì)把 WAR 解壓到webapps/下同名目錄但如果舊目錄里的文件時(shí)間戳比 WAR 包里的新Tomcat 不會(huì)覆蓋。解決每次更新 WAR 包之前先刪掉舊的解壓目錄和 WAR 文件# 停止 Tomcat /opt/tomcat/current/bin/shutdown.sh # 刪除舊的應(yīng)用目錄和 WAR 包 rm -rf /opt/tomcat/current/webapps/yourapp rm -f /opt/tomcat/current/webapps/yourapp.war # 拷貝新 WAR 包 cp yourapp.war /opt/tomcat/current/webapps/ # 啟動(dòng) /opt/tomcat/current/bin/startup.sh或者把unpackWARs改成false讓 Tomcat 直接從 WAR 包運(yùn)行但這樣 JSP 編譯會(huì)變慢而且有些框架讀取WEB-INF下資源的方式會(huì)出問(wèn)題。我一般還是用true手動(dòng)清理。4.5 坑五日志文件 catalina.out 無(wú)限增長(zhǎng)現(xiàn)象磁盤空間被catalina.out占滿Tomcat 寫不進(jìn)日志后行為異常。原因catalina.out是 Tomcat 的標(biāo)準(zhǔn)輸出重定向文件不會(huì)自動(dòng)切割。7.0.108 的logging.properties里配置的是java.util.logging但catalina.out不受它管理。解決用logrotate做切割# /etc/logrotate.d/tomcat /opt/tomcat/current/logs/catalina.out { daily rotate 7 missingok compress copytruncate notifempty }copytruncate是關(guān)鍵它先復(fù)制文件再清空原文件不需要重啟 Tomcat。rotate 7保留 7 天。配好之后手動(dòng)跑一次logrotate -f /etc/logrotate.d/tomcat驗(yàn)證。5. 裝完怎么驗(yàn)證從端口到 JSP 到 WAR 部署的完整檢查鏈裝完 Tomcat 之后別急著丟 WAR 包進(jìn)去先按這個(gè)順序走一遍驗(yàn)證能省掉后面很多排查時(shí)間。第一步檢查進(jìn)程和端口# 確認(rèn) Tomcat 進(jìn)程存在 ps -ef | grep tomcat | grep -v grep # 確認(rèn) 8080 端口監(jiān)聽 ss -tlnp | grep 8080如果進(jìn)程在但端口沒(méi)監(jiān)聽說(shuō)明Connector配置有問(wèn)題去catalina.out里搜BindException或LifecycleException。第二步訪問(wèn)默認(rèn)首頁(yè)curl -I http://localhost:8080/ # 應(yīng)該返回 HTTP/1.1 200 OK如果返回 404檢查webapps/ROOT目錄是否存在。7.0.108 的 zip 包里自帶ROOT應(yīng)用如果被誤刪了可以從conf/Catalina/localhost/下看看有沒(méi)有殘留配置。第三步驗(yàn)證 JSP 編譯在webapps/ROOT/下建一個(gè)test.jsp% page contentTypetext/html;charsetUTF-8 languagejava % html headtitle環(huán)境驗(yàn)證/title/head body % out.println(JSP 編譯正常當(dāng)前時(shí)間 new java.util.Date()); out.println(JAVA_HOME System.getProperty(java.home)); % /body /html訪問(wèn)http://localhost:8080/test.jsp如果能看到時(shí)間輸出說(shuō)明 JSP 編譯鏈路通了。如果報(bào) 500回到 4.1 節(jié)檢查tools.jar。第四步部署一個(gè)測(cè)試 WAR 包拿一個(gè)最簡(jiǎn)單的 WAR 包丟進(jìn)webapps/觀察日志# 實(shí)時(shí)監(jiān)控日志 tail -f /opt/tomcat/current/logs/catalina.out # 看到類似輸出說(shuō)明部署成功 # Deployment of web application archive /opt/tomcat/current/webapps/test.war has finished in 1,234 ms然后訪問(wèn)http://localhost:8080/test/確認(rèn)應(yīng)用響應(yīng)正常。第五步檢查日志目錄權(quán)限和磁盤空間# 確認(rèn) logs 目錄可寫 touch /opt/tomcat/current/logs/test.log rm /opt/tomcat/current/logs/test.log # 檢查磁盤使用率 df -h /opt/tomcat/我一般會(huì)在部署完成后跑一個(gè)簡(jiǎn)單的壓力測(cè)試用ab或者wrk打 1000 個(gè)請(qǐng)求看線程池和內(nèi)存有沒(méi)有異常# 安裝 apache2-utils 后使用 ab ab -n 1000 -c 50 http://localhost:8080/關(guān)注Failed requests和Time per request兩個(gè)指標(biāo)。如果失敗率超過(guò) 1%去logs/localhost_access_log里看具體是哪些請(qǐng)求掛了。從那以后我每次裝完 Tomcat不管多急都會(huì)把這五步走一遍。尤其是 JSP 編譯那步很多問(wèn)題在部署 WAR 之前就能暴露出來(lái)比等到應(yīng)用上線后才發(fā)現(xiàn)要省事得多。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取