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