Tomcat系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.htmlphp
tomcat通常只提供動態資源處理功能,而靜態資源的請求則交給獨立的apache/httpd或nginx來處理。但tomcat與外界通訊的惟一組件是鏈接器Connector,所以動態請求要轉發給tomcat時,須要和Connector通訊。Connector與外界通訊的協議有兩種:http/ajp。css
tomcat的Connector組件支持兩種協議類型的鏈接:http、ajp。html
其中http又分爲http/1.1和http/2,但由於Servlet的阻塞特性,使得每一個http/2的請求都須要單獨的容器線程負責處理,所以目前使用http/2的性能還不好。而AJP又有ajp十二、ajp13和ajp14,其中ajp12的功能太簡陋,ajp14又處於試驗期,所以目前都適用ajp13,或者稱爲ajp 1.3。前端
基於IO模型,Connector又分爲NIO/NIO2/APR三種類型。java
設置鏈接器協議的方式以下:nginx
<!-- 定義使用Http協議的鏈接器,其中"HTTP/1.1"表示自動選擇NIO/NIO2/APR -->
<connector port="8080" protocol="HTTP/1.1">
<connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol">
<connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol">
<connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol">
<!-- 定義使用AJP協議的鏈接器,其中"AJP/1.3"表示自動選擇NIO/NIO2/APR -->
<Connector port="8009" protocol="AJP/1.3"/>
<Connector port="8009" protocol="org.apache.coyote.ajp.AjpNioProtocol"/>
<Connector port="8009" protocol="org.apache.coyote.ajp.AjpNio2Protocol"/>
<Connector port="8009" protocol="org.apache.coyote.ajp.AjpAprProtocol"/>
Tomcat鏈接器與外界通訊的兩種協議中,ajp只能和apache基金會開發的部分項目通訊,例如httpd。要與非apache基金會開發的程序通訊,必須使用http協議。所以,tomcat+nginx時,tomcat鏈接器的協議類型只能是http。web
對於tomcat+httpd,tomcat端的鏈接器既可使用http,也可使用ajp協議。但在httpd端,也支持多種通訊模塊,最經常使用的是mod_proxy和mod_jk。apache
tomcat+httpd時,幾個須要注意的點:vim
所以,若是知足業務需求,建議使用mod_proxy,它配置起來比mod_jk要簡便的多,且可定製的功能更多。後端
稍做總結,apache和tomcat通訊的實現方式有3種:
apache:mod_jk <--> tomcat:ajp(雖然能夠和http配合,但不建議)
apache:mod_proxy <--> tomcat:ajp或http
下圖是httpd/nginx+tomcat在動靜分離時一般使用的架構模型。
左邊的模型中,nginx/httpd須要處理靜態請求,並將動態請求轉發給tomcat,同時實現負載均衡。右邊的模型中,添加一層反向代理層,不管是靜態請求仍是動態請求都負載到各自的服務器組。後文的實驗都採用左邊的架構模型來完成。
測試環境以下:
[root@xuexi ~]# echo "[nginx]
name=nginx
baseurl=http://nginx.org/packages/centos/6/x86_64/
enable=1
gpgcheck=0" >/etc/yum.repos.d/nginx.repo
[root@xuexi ~]# yum install -y nginx
[root@xuexi ~]# vim /etc/nginx/conf.d/tomcat.conf
upstream tomcat_servers {
server 192.168.100.22:8080 weight=1 max_fails=2 fail_timeout=2;
server 192.168.100.23:8080 weight=2 max_fails=2 fail_timeout=2;
}
server {
listen 80;
server_name 192.168.100.17;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location ~* \.(jsp|jspx|do) {
proxy_pass http://tomcat_servers;
}
}
啓動nginx和tomcatA、tomcatB。而後在瀏覽器中分別訪問靜態資源和動態資源。
http://192.168.100.17
http://192.168.100.17/index.jsp
其中第二個訪問的是動態資源,頁面結果以下:
發現這個歡迎頁面缺失了不少內容。其實這些缺失的內容都是定義在index.jsp中的靜態資源,例如某些圖片。之因此會缺失,是由於客戶端發送請求給nginx後,nginx將請求轉發給tomcat,tomcat翻譯index.jsp爲java源文件,而後執行該servlet。執行servlet時,將所需響應數據都回應給nginx,包括index.jsp中定義的圖片連接標籤。當客戶端收到這一次的響應數據後,還會繼續去請求圖片,可是這是靜態請求,nginx會本身處理而不會轉發給tomcat,但nginx自己不知道圖片在何處(tomcat才知道)。所以對於圖片部分,nginx將返回404錯誤,使得客戶端顯示的頁面缺失了一部分。
在生產環境下,不須要擔憂這樣的二次靜態請求缺失問題,由於會將圖片等靜態數據存放在某個位置,並配置好nginx如何找到這些靜態數據。例如,上面的頁面中缺失了tomcat.{png,css,gif}等靜態文件,將它們從tomcat服務器的$CATALINA_HOME/webapps/ROOT/目錄下拷貝到nginx服務器的/usr/share/nginx/html目錄(上面配置文件的root指令指定的位置)下,並賦予讀取權限。再去訪問,就能正確顯示圖片。
測試環境以下:
使用mod_jk模塊和tomcat鏈接時,tomcat的鏈接器通常都使用ajp協議類型。
mod_jk不是apache httpd的原生模塊,而是相似於第三方模塊,所以須要額外編譯mod_jk模塊到httpd中,就像將php模塊添加到httpd中同樣。
當前最新穩定版的mod_jk是1.2.42版本。
mod_jk下載地址:http://tomcat.apache.org/download-connectors.cgi。
mod_jk官方手冊:http://tomcat.apache.org/connectors-doc/。
httpd要擴展模塊須要藉助apxs,它是httpd的開發包httpd-devel中工具,因此先要安裝httpd-devel。若是是編譯安裝的httpd,則devel包已經裝好,若是是yum安裝,則須要額外安裝httpd-devel包。
此處爲了方便,httpd使用yum安裝。因此編譯mod_jk的方式以下:
yum -y install httpd httpd-devel tar xf tomcat-connectors-1.2.42-src.tar.gz cd tomcat-connectors-1.2.42-src/native/ ./configure --with-apxs=/usr/bin/apxs --prefix=/usr/local/tomcat/mod_jk make && make install
此處暫先配置httpd與其中一個tomcat(192.168.100.22)鏈接。後文在說明負載均衡時再引入另外一個tomcat。
先提供一個額外的httpd配置文件。
[root@xuexi ~]# cat /etc/httpd/conf.d/mod_jk.conf
LoadModule jk_module modules/mod_jk.so
JkWorkersFile /etc/httpd/conf.d/workers.properties
JkLogFile logs/mod_jk.log
JkLogLevel debug
######### "JkMount /* TomcatA" will send all request to TomcatA ########
JkMount /*.jsp TomcatA
JkMount /status/* statA
JkUnMount /images/* TomcatA
JkUnMount /css/*.* TomcatA
JkUnMount /css_js/* TomcatA
JkUnMount /*.html TomcatA
JkUnMount /*.js TomcatA
mod_jk的配置文件官方手冊:http://tomcat.apache.org/connectors-doc/reference/apache.html。如下是幾個經常使用的指令說明。
LoadModule
指令用於裝載mod_jk相關模塊,除此以外還須要在httpd的配置文件中設置其它一些指令來配置其工做屬性。如:JkWorkersFile
用於指定保存了worker相關工做屬性定義(見下文)的配置文件。JkLogFile
用於指定mod_jk模塊的日誌文件。JkLogLevel
用於指定日誌級別(info,error,debug),此外還可使用JkRequestLogFormat自定義日誌信息格式。JkMount
(格式:JkMount )則用於控制URL與Tomcat workers的對應關係。能夠理解爲轉發請求的意思,例如"/status/*"表示url地址後加上/status/可轉發至statA這個worker上。注意,JkMount匹配的URL是相對的。若是JkMount指令放在Location指令中,如<Location /app>
,則JkMount將從/app的後面開始匹配。JkMount和JkUnMount是很重要的指令,mod_jk性能之因此比mod_proxy好,就是由於經過這兩個指令能夠實現動靜分離,使得只將動態請求轉發給tomcat。其中JkMount指定要轉發給tomcat處理的請求,JkUnMount指定明確不轉發給tomcat而是在本地處理的請求。雖然不指定JkUnMount時,也表示不轉發給tomcat,但若是有重疊時,則應該指定JkUnMount。例以下面的例子,除了/myapp/下的js文件,其餘都轉發給tomcat1處理。
JkMount /myapp/* tomcat1
JkUnMount /myapp/*.js tomcat1
對於apache來講,每個後端Tomcat實例中的engine均可以視做一個worker,而每個worker的地址、Connector的端口等信息都須要在apache端指定以即可以識別並使用這些worker。配置這些信息的文件一般爲"workers.properties",其具體路徑是使用前面介紹過的JkWorkersFile指定的。在apache啓動時,mod_jk會掃描此文件獲取每個worker配置信息。如這裏使用/etc/httpd/conf.d/workers.properties
。
workers.properties文件通常由兩類指令組成:一是mod_jk能夠鏈接的各worker名稱列表,二是每個worker的屬性配置信息。詳細的配置方法見官方手冊:http://tomcat.apache.org/connectors-doc/reference/workers.html。
如下是和上述/etc/httpd/conf.d/mod_jk.conf中配置相對應的/etc/httpd/conf.d/workers.properties。
[root@xuexi tomcat]# cat /etc/httpd/conf.d/workers.properties
worker.list=TomcatA,statA
worker.TomcatA.type=ajp13
worker.TomcatA.host=192.168.100.22
worker.TomcatA.port=8009
worker.TomcatA.lbfactor=1
worker.statA.type = status
關於worker的配置,它們分別遵循以下使用語法。
worker.list = <a comma separated list of worker_name>
worker.<worker_name>.<property>=<property value>
其中worker.list指令能夠重複指定屢次。worker_name是Tomcat中engine組件中jvmRoute屬性的值(jvmRoute能夠不指定,此時worker_name僅用於標識worker)。
根據工做機制的不一樣,worker有多種不一樣的類型,每一個worker都須要指定其類型,即設定woker..type項。常見的類型以下:其中ajp13是默認值。
因爲status是狀態監控頁面,因此應該保證其安全性,能夠在httpd的配置文件中加入如下控制列表:
# 注意,必須加上尾隨斜線,由於在mod_jk.conf中已經明確了"/status/*"
# For http 2.2
<Location /status/>
Order deny,allow
Deny from all
Allow from 192.168.100.0/24
</Location>
# For http 2.4
<Location /status/>
Requrie ip 192.168.100
</Location>
除了type屬性外,worker其它常見的屬性有:
除了type屬性外,worker其它常見的屬性有:
另外,在負載均衡模式中專用的屬性還有:
至此,一個基於mod_jk模塊與後端名爲TomcatA的worker通訊的配置已經完成,重啓httpd服務便可生效。
測試:在瀏覽器中輸入
http://192.168.100.17/
http://192.168.100.17/index.jsp
http://192.168.100.17/status/
若是都能獲取頁面,則表示apache經過mod_jk和tomcat基於ajp協議類型的鏈接已經成功。
使用mod_jk實現tomcat的負載均衡有一個好處,tomcat上能夠禁用http協議(將監聽此協議的Connector配置刪除便可),防止外界直接經過http請求tomcat。
配置apache,使其支持負載均衡,修改/etc/httpd/conf.d/mod_jk.conf爲以下內容:
LoadModule jk_module modules/mod_jk.so
JkWorkersFile /etc/httpd/conf.d/workers.properties
JkLogFile logs/mod_jk.log
JkLogLevel notice
JkMount /*.jsp TomcatLB
JkMount /status/* statA
編輯/etc/httpd/conf.d/workers.properties,修改成以下內容:爲測試負載效果,不啓用stick_session。
worker.list=TomcatLB,statA
worker.statA.type=status
worker.TomcatLB.type=lb
worker.TomcatLB.sticky_session=false
worker.TomcatLB.balance_workers=TomcatA,TomcatB
worker.TomcatA.type=ajp13
worker.TomcatA.host=192.168.100.22
worker.TomcatA.port=8009
worker.TomcatA.lbfactor=5
worker.TomcatB.type=ajp13
worker.TomcatB.host=192.168.100.23
worker.TomcatB.port=8009
worker.TomcatB.lbfactor=10
在mod_jk負載均衡中,後端tomcat的engine組件須要添加jvmRoute參數,該參數會爲當前server實例設置全局唯一標識符,所以每個實例的jvmRoute的值均不能相同,且jvmRoute的值必須等於balance_workers的成員值。對於上面的配置,Engine應該以下設置:此處還修改了name,但這不是要求要修改的。
<!-- 在tomcatA上設置 -->
<Engine name="Standalone" defaultHost="localhost" jvmRoute="TomcatA">
<!-- 在tomcatB上設置 -->
<Engine name="Standalone" defaultHost="localhost" jvmRoute="TomcatB">
爲了演示效果,在TomcatA部署一個應用程序test。
[root@xuexi tomcat]# mkdir -p /usr/local/tomcat/webapps/test/WEB-INF/{classes,lib}
添加index.jsp,內容以下:
[root@xuexi tomcat]# cat /usr/local/tomcat/webapps/test/index.jsp
<%@ page language="java" %>
<html>
<head><title>TomcatA</title></head>
<body>
<h1><font color="red">TomcatA </font></h1>
<table align="centre" border="1">
<tr>
<td>Session ID</td>
<% session.setAttribute("abc","abc"); %>
<td><%= session.getId() %></td>
</tr>
<tr>
<td>Created on</td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
</html>
在TomcatB一樣也部署一個應用程序test。以下:
[root@xuexi tomcat]# mkdir -p /usr/local/tomcat/webapps/test/WEB-INF/{classes,lib}
[root@xuexi tomcat]# cat /usr/local/tomcat/webapps/test/index.jsp
<%@ page language="java" %>
<html>
<head><title>TomcatB</title></head>
<body>
<h1><font color="blue">TomcatB </font></h1>
<table align="centre" border="1">
<tr>
<td>Session ID</td>
<% session.setAttribute("abc","abc"); %>
<td><%= session.getId() %></td>
</tr>
<tr>
<td>Created on</td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
</html>
重啓httpd、tomcatA、tomcatB對應的服務程序。在瀏覽器中輸入192.168.100.17/test/index.jsp測試負載均衡是否生效。
測試時,輪調兩次tomcatB後輪調一次tomcatA。並且能夠發現每次輪詢時Session ID每次都是變化的,由於沒有開啓sticky_session,因此session沒有進行綁定。
要綁定會話,將worker.properties中的sticky_session設置爲true便可。
測試環境以下:
當httpd端採用mod_proxy和tomcat鏈接時,能夠採用ajp或http協議進行鏈接。
要使用mod_proxy與Tomcat鏈接,須要apache已經裝載mod_proxy、mod_proxy_http、mod_proxy_ajp和proxy_balancer_module(實現Tomcat負載均衡時用到)等模塊。使用rpm包安裝的httpd通常默認已經啓用它們,若是是編譯httpd,則在編譯選項中加上如下對應幾項:
--enable-proxy --enable-proxy-http --enable-proxy-ajp --enable-proxy-balancer
若是是已經編譯好的Httpd,則可使用apxs工具,向httpd中添加這幾個新模塊。添加方法見:httpd添加新模塊。
確保proxy相關的模塊已經加載了。
[root@xuexi ~]# httpd -M | grep proxy
Syntax OK
proxy_module (shared)
proxy_balancer_module (shared)
proxy_ftp_module (shared)
proxy_http_module (shared)
proxy_ajp_module (shared)
proxy_connect_module (shared)
基於ajp鏈接協議和tomcat鏈接時,向httpd添加如下配置文件。若是要基於http協議鏈接tomcat,將上面配置文件的ajp協議改成http協議,並修改端口便可。
[root@xuexi ~]# cat /etc/httpd/conf.d/ajp.conf
<Location /status>
SetHandler balancer-manager
Proxypass ! # 表示此Location的URL不進行反向代理
Require ip 192.168.100
</Location>
ProxyVia Off
ProxyRequests Off
ProxyPreserveHost Off
ProxyPassMatch "^/(.*\.jsp)$" ajp://192.168.100.22:8009/$1
ProxyPassReverse "^/(.*\.jsp)$" ajp://192.168.100.22:8009/$1
<Proxy *>
Require all granted
</Proxy>
重啓httpd。注意,重啓前將前面mod_jk實驗的配置文件刪除掉。
關於如上apache配置的幾個指令,解釋以下。httpd反向代理的詳細內容,可參見:詳細分析apache httpd反向代理的用法。
ProxyVia {On|Off|Full|Block}
:用於控制在http首部是否使用"Via:",主要用於在多級代理中控制代理請求的流向。默認爲Off,即不啓用此功能;On表示每一個請求和響應報文均添加"Via:";Full表示每一個"Via:"行都會添加當前apache服務器的版本號信息;Block表示每一個代理請求報文中的"Via:"都會被移除。ProxyRequests {On|Off}
:是否開啓apache正向代理的功能;若是爲apache設置了ProxyPass即反向代理,則必須將ProxyRequests設置爲Off。ProxyPreserveHost {On|Off}
:若是啓用此功能,代理會將用戶請求報文中的"Host:"行發送給後端的服務器,而再也不使用ProxyPass指定的服務器IP地址。若是後端一個IP上可能會有多個虛擬主機,則須要開啓此項明確轉發給哪臺虛擬主機,不然就無需打開此功能。ProxyPassReverse
:在反向代理環境中必須使用此指令避免重定向報文繞過proxy服務器,屬性設置爲ProxyPass同樣基本上就能夠。ProxyPass [path] !|[url [key=value key=value ...]]
:將後端服務器某URL與當前服務器的某虛擬路徑關聯起來做爲提供服務的路徑,path爲當前服務器上的某虛擬路徑,url爲後端服務器上某URL路徑。使用此指令時必須將ProxyRequests的值設置爲Off。須要注意的是,若是path以"/"結尾,則對應的url也必須以"/"結尾,反之亦然。ProxyPassMatch [regex] !|url [key=value [key=value ...]]
:正則格式的ProxyPass。關於httpd反向代理的負載均衡配置方式,參見詳細分析apache httpd反向代理的用法。
在httpd.conf中配置以下內容:
<proxy balancer://TomcatLB>
BalancerMember ajp://192.168.100.38:8009 loadfactor=5
BalancerMember ajp://192.168.100.36:8009 loadfactor=10
</proxy>
ProxyVia Off
ProxyRequests Off
ProxyPreserveHost Off
ProxyPassMatch "^/(.*\.jsp)$" balancer://TomcatLB/$1
ProxyPassReverse "^/(.*\.jsp)$" balancer://TomcatLB/$1
<Proxy *>
Require all granted
</Proxy>
<Location /status>
SetHandler balancer-manager
Proxypass !
Require ip 192.168.100
</Location>
重啓httpd並在瀏覽器中輸入192.168.100.17/test/index.jsp
測試,測試時會輪調兩次tomcatB再輪調一次tomcatA。
若是要實現session粘滯(綁定),則修改httpd配置文件以下:
<proxy balancer://TomcatLB>
BalancerMember ajp://192.168.100.22:8009 loadfactor=5 route=TomcatA
BalancerMember ajp://192.168.100.23:8009 loadfactor=10 route=TomcatB
ProxySet lbmethod=byrequets
</proxy>
ProxyVia Off
ProxyRequests Off
ProxyPreserveHost Off
ProxyPassMatch "^/(.*\.jsp)$" balancer://TomcatLB/$1 stickysession=JSESSIONID
ProxyPassReverse "^/(.*\.jsp)$" balancer://TomcatLB/$1 stickysession=JSESSIONID
<Proxy *>
Require all granted
</Proxy>
<Location /status>
SetHandler balancer-manager
Proxypass !
Require ip 192.168.100
</Location>
而後分別配置tomcatA和tomcatB的engine組件,分別加上jvmRoute="TomcatA"和jvmRoute="TomcatB"屬性。
<Engine name="Catalina" defaultHost="localhost" jvmRoute="TomcatA">
<Engine name="Catalina" defaultHost="localhost" jvmRoute="TomcatB">
重啓httpd和tomcatA、tomcatB,而後測試結果,再測試時同一客戶端將老是獲得同一個結果,不會出現負載均衡。