Tomcat結合Apache、Nginx實現高效能的web伺服器
一、Tomcat為什麼需要與apache、nginx一起結合使用?
Tomcat雖然是一個servlet和jsp容器,但是它也是一個輕量級的web伺服器。它既可以處理動態內容,也可以處理靜態內容。不過,tomcat的最大優勢在於處理動態請求,處理靜態內容的能力不如apache和nginx,並且經過測試發現,tomcat在高併發的場景下,其接受的最大併發連線數是由限制的,連線數過多會導致tomcat處於"僵死"狀態,因此,在這種情況下,我們可以利用nginx的高併發,低消耗的特點與tomcat一起使用。因此,tomcat與nginx、apache結合使用共有如下幾點原因:
1、tomcat處理html的能力不如Apache和nginx,tomcat處理靜態內容的速度不如apache和nginx。
2、tomcat接受的最大併發數有限,接連線數過多,會導致tomcat處於"殭屍"狀態,對後續的連線失去響應,需要結合nginx一起使用。
通常情況下,tomcat與nginx、Apache結合使用,nginx、apache既可以提供web服務,也可以轉發動態請求至tomcat伺服器上。但在一個高效能的站點上,通常nginx、apache只提供代理的功能,也就是轉發請求至tomcat伺服器上,而對於靜態內容的響應,則由前端負載均衡器來轉發至專門的靜態伺服器上進行處理。其架構類似於如下圖:
在這種架構中,當haproxy或nginx作為前端代理時,如果是靜態內容,如html、css等內容,則直接交給靜態伺服器處理;如果請求的圖片等內容,則直接交給圖片伺服器處理;如果請求的是動態內容,則交給tomcat伺服器處理,不過在tomcat伺服器上,同時執行著nginx伺服器,此時的nginx作為靜態伺服器,它不處理靜態請求,它的作用主要是接受請求,並將請求轉發給tomcat伺服器的,除此之外,nginx沒有任何作用。
二、tomcat的聯結器協議
tomcat和前端web伺服器(nginx或Apache)之間的通訊時靠聯結器協議來完成的,tomcat共支援4中聯結器協議,分別為:
1) HTTP聯結器
2) SSL聯結器
3) AJP 1.3聯結器
4) proxy聯結器(好像這個沒用了)
也可以歸檔為支援2種聯結器協議:AJP和HTTP,它們用來在Web伺服器和Tomcat之間進行資料傳輸,並提供相應的控制命令。
AJP(Apache JServ Protocol)協議
目前正在使用的AJP協議的版本是通過JK和JK2聯結器提供支援的AJP13,它基於二進位制的格式在Web伺服器和Tomcat之間傳輸資料,而此前的版本AJP10和AJP11則使用文字格式傳輸資料。由於這種協議是二進位制的,因此在處理請求時的效率比http協議要高。目前常用AJP協議的版本是1.3,它主要有以下特徵:
1) 在快速網路有著較好的效能表現,支援資料壓縮傳輸;
2) 支援SSL,加密及客戶端證書;
3) 支援Tomcat例項叢集;
4) 支援在apache和tomcat之間的連線的重用,不適用於與nginx通訊;
HTTP協議:誠如其名稱所表示,使用HTTP或HTTPS協議在Web伺服器和Tomcat之間建立通訊,此時,Tomcat就是一個完全功能的HTTP伺服器,它需要監聽在某埠上以接收來自於前端伺服器的請求,對於動態請求需要轉交給servlet容器進行處理。如果前端是nginx做代理的話,那麼nginx和tomcat之間的通訊只能使用http協議了,而不能使用AJP協議。
http聯結器也有3種類型
Tomcat的HTTP聯結器有三種:
1) 基於java的HTTP/1.1聯結器,這也是Tomcat6預設使用的聯結器,即Coyote;它是Tomcat作為standalone模式工作時所用到的聯結器,可直接響應來自使用者瀏覽器的關於JSP、servlet和HTML的請求;此聯結器是一個Java類,定義在server.xml當中,預設使用8080埠;
2) Java開發的高效能NIO HTTP/1.1聯結器,它支援非阻塞式IO和Comnet,在基於庫向tomcat發起請求時,此聯結器表現不俗;但其實現不太成熟,有嚴重bug存在;
3) C/C++開發的native APR HTTP/1.1聯結器;在負載較大的場景中,此聯結器可以提供非常好的效能;APR即Apache Portable Runtime,它是一個能讓開發者採用與平臺無關的風格的方式來開發C/C++程式碼本地庫,它能夠很好的跨Windows,;
預設tomcat的採用的是第1種聯結器型別。
實驗前的準備步驟:
1)、由於我是將tomcat和nginx在不同的伺服器上配置的,因此,tomcat的ip地址為192.168.1.103,nginx的ip地址為172.16.1.100
2)、關閉系統上的防火牆和selinux
3)、nginx系統上的ip地址和閘道器配置省略了
三、tomcat與nginx一起結合使用
tomcat與nginx為什麼需要一起使用的原因就不在闡述了,前面已經說得很清楚了,這裡直接給出操作步驟。
1、安裝nginx
# yum -y install nginx
2、編輯並配置nginx的配置檔案/etc/nginx/nginx.conf
在該配置檔案中新增如下配置即可
upstream tomcat {
server 172.16.1.100 weight=1 max_fails=3 fail_timeout =10s;
location ~* \.(jsp|do)$ {
proxy_pass http://tomcat;
}
nginx上只需要新增轉發動態請求的配置即可。
3、啟動nginx服務
# service nginx start
4、安裝配置tomcat
tomcat的安裝這裡就不在詳述了。這裡只給出tomcat的配置
編輯tomcat的配置檔案server.xml,新增如下內容:
<Host name="www.xsl.com" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Context docBase="app1" path="a" reloadable="true" />
</Host>
建立相應的目錄及其檔案
# mkdir -pv /usr/local/tomcat7/app1/a
# cd /usr/local/tomcat7/app1/a
# vim index.jsp
新增如下測試程式碼
<%@ page language="java" %>
<%@ page import="java.util.*" %>
<html>
<head>
<title>JSP test page.</title>
</head>
<body>
<% out.println("Hello,world!"); %>
</body>
</html>
其實整個內容也就是顯示"hello world"。
啟動tomcat
# service tomcat start
5、測試訪問http://192.168.1.103/app1/a/index.jsp
四、tomcat結合apache一起使用
apache與tomcat結合使用時,有2種模組可以與tomcat進行通訊,這兩種模組分別是mod_proxy模組和mod_jk模組。mod_proxy模組是apache自帶的模組,因此要使用該模組,只需要啟用該模組就行了。而mod_jk模組,需要下載並進行手動編譯,使其成為apache的一個模組。
mod_proxy模組可以基於http協議與tomcat進行通訊,也可以基於ajp協議與tomcat進行通訊;而mod_jk模組只能基於ajp協議與tomcat進行通訊。
apache使用mod_proxy模組與tomcat進行通訊
其步驟如下:
(1)、apache使用mod_proxy模組與tomcat通訊時,需要載入proxy_module、proxy_http_module、proxy_ajp_module、proxy_balancer_module(負載均衡時需要使用該模組)等這些模組。
# yum -y install httpd
檢視apache是否已經啟用該模組了。
# httpd -M | grep proxy
httpd: Could not reliably determine the server's fully qualified domain name, using localhost.localdomain for ServerName
proxy_module (shared)
proxy_balancer_module (shared)
proxy_ftp_module (shared)
proxy_http_module (shared)
proxy_ajp_module (shared)
proxy_connect_module (shared)
(2)、編輯httpd的配置檔案/etc/httpd/conf.d/proxy.conf檔案,新增如下內容:
proxyvia off
proxypreservehost on
proxyrequests off
proxypass / ajp://172.16.1.100/
proxypa***everse / ajp://172.16.1.100/
如果你的http版本是2.4的話,其配置如下:
proxyvia off
proxypreservehost on
proxyrequests off
<Proxy *>
Require all granted
</Proxy>
proxypass / ajp://172.16.1.100/
proxypa***everse / ajp://172.16.1.100/
<Location / >
Require all granted
</Location>
如果要想基於http聯結器協議與tomcat工作的話,只需要將ajp改為http即可。其他的配置都不變。
關於如上apache指令的說明:
ProxyPreserveHost {On|Off}:如果啟用此功能,代理會將使用者請求報文中的Host:行傳送給後端的伺服器,而不再使用ProxyPass指定的伺服器地址。如果想在反向代理中支援虛擬主機,則需要開啟此項,否則就無需開啟此功能。
ProxyVia {On|Off|Full|Block}:用於控制在http首部是否使用Via:,主要用於在多級代理中控制代理請求的流向。預設為Off,即不啟用此功能;On表示每個請求和響應報文均新增Via:;Full表示每個Via:行都會添加當前apache伺服器的版本號資訊;Block表示每個代理請求報文中的Via:都會被移除。
ProxyPa***everse:用於讓apache調整HTTP重定向響應報文中的Location、Content-Location及URI標籤所對應的URL,在反向代理環境中必須使用此指令避免重定向報文繞過proxy伺服器。
ProxyRequests {On|Off}:是否開啟apache正向代理的功能;啟用此項時為了代理http協議必須啟用proxy_http_module模組。同時,如果為apache設定了ProxyPass,則必須將ProxyRequests設定為Off。
ProxyPass [path] !|url [key=value key=value ...]]:將後端伺服器某URL與當前伺服器的某虛擬路徑關聯起來作為提供服務的路徑,path為當前伺服器上的某虛擬路徑,url為後端伺服器上某URL路徑。使用此指令時必須將ProxyRequests的值設定為Off。需要注意的是,如果path以“/”結尾,則對應的url也必須以“/”結尾,反之亦然。
另外,mod_proxy模組在httpd 2.1的版本之後支援與後端伺服器的連線池功能,連線在按需建立在可以儲存至連線池中以備進一步使用。連線池大小或其它設定可以通過在ProxyPass中使用key=value的方式定義。常用的key如下所示:
◇ min:連線池的最小容量,此值與實際連線個數無關,僅表示連線池最小要初始化的空間大小。
◇ max:連線池的最大容量,每個MPM都有自己獨立的容量;都值與MPM本身有關,如Prefork的總是為1,而其它的則取決於ThreadsPerChild指令的值。
◇ loadfactor:用於負載均衡叢集配置中,定義對應後端伺服器的權重,取值範圍為1-100。
◇ retry:當apache將請求傳送至後端伺服器得到錯誤響應時等待多長時間以後再重試。單位是秒鐘。
(3)、啟動httpd服務
# service httpd start
(4)、安裝配置tomcat
tomcat的安裝這裡就不在詳述了。這裡只給出tomcat的配置
編輯tomcat的配置檔案server.xml,新增如下內容:
<Host name="www.xsl.com" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Context docBase="app1" path="a" reloadable="true" />
</Host>
建立相應的目錄及其檔案
# mkdir -pv /usr/local/tomcat7/app1/a
# cd /usr/local/tomcat7/app1/a
# vim index.jsp
新增如下測試程式碼
<%@ page language="java" %>
<%@ page import="java.util.*" %>
<html>
<head>
<title>JSP test page.</title>
</head>
<body>
<% out.println("Hello,world!"); %>
</body>
</html>
其實整個內容也就是顯示"hello world"。
啟動tomcat
# service tomcat start
(5)、測試訪問http://192.168.1.103/app1/a/index.jsp
apache使用mod_jk模組與tomcat進行通訊
mod_jk是ASF的一個專案,是一個工作於apache端基於AJP協議與Tomcat通訊的聯結器,它是apache的一個模組,是AJP協議的客戶端(服務端是Tomcat的AJP聯結器)。
(1)、編譯安裝mod_jk
下載地址:http://tomcat.apache.org/connectors-doc/
# yum -y install httpd
# yum -y install httpd-devel
# tar xf tomcat-connectors-1.2.41-src.tar.gz
# cd /root/tomcat-connectors-1.2.41-src/native
# ./configure --with-apxs (如果沒有apxs程式,需要安裝httpd-devel包)
# make && make install
(2)、編輯httpd的配置檔案/etc/httpd/conf.d/httpd-jk.conf
apache要使用mod_jk聯結器,需要在啟動時載入此聯結器模組。為了便於管理與mod_jk模組相關的配置,這裡使用一個專門的配置檔案/etc/httpd/conf.d/httpd-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
JkMount /status/ stat1
除了需要使用LoadModule指令在apache中裝載模組外,mod_jk還需要在apache的主配置檔案中設定其它一些指令來配置其工作屬性。如JkWorkersFile則用於指定儲存了worker相關工作屬性定義的配置檔案,JkLogFile則用於指定mod_jk模組的日誌檔案,JkLogLevel則可用於指定日誌的級別(info, error, debug),此外還可以使用JkRequestLogFormat自定義日誌資訊格式。而JkMount(格式: JkMount <URL to match> <Tomcat worker name>)指定則用於控制URL與Tomcat workers的對應關係。
/etc/httpd/conf.d/workers.properties的內容如下:
worker.list=TomcatA,stat1
worker.TomcatA.port =8009
worker.TomcatA.host=172.16.1.100
worker.TomcatA.type=ajp13
worker.TomcatA.lbfactor=1
worker.stat1.type=status
根據其工作機制的不同,worker有多種不同的型別,這是需要為每個worker定義的一項屬性woker.<work name>.type。常見的型別如下:
◇ ajp13:此型別表示當前worker為一個執行著的Tomcat例項。
◇ lb:lb即load balancing,專用於負載均衡場景中的woker;此worker並不真正負責處理使用者請求,而是將使用者請求排程給其它型別為ajp13的worker。
◇status:使用者顯示分散式環境中各實際worker工作狀態的特殊worker,它不處理任何請求,也不關聯到任何實際工作的worker例項。具體示例如請參見後文中的配置。
worker其它常見的屬性說明:
◇ host:Tomcat 7的worker例項所在的主機;
◇ port:Tomcat 7例項上AJP1.3聯結器的埠;
◇ connection_pool_minsize:最少要儲存在連線池中的連線的個數;預設為pool_size/2;
◇ connection_pool_timeout:連線池中連線的超時時長;
◇ mount:由當前worker提供的context路徑,如果有多個則使用空格格開;此屬性可以由JkMount指令替代;
◇ retries:錯誤發生時的重試次數;
◇ socket_timeout:mod_jk等待worker響應的時長,預設為0,即無限等待;
◇ socket_keepalive:是否啟用keep alive的功能,1表示啟用,0表示禁用;
◇ lbfactor:worker的權重,可以在負載均衡的應用場景中為worker定義此屬性
(3)、啟動httpd服務
# service httpd start
(4)、安裝配置tomcat
tomcat的安裝這裡就不在詳述了。這裡只給出tomcat的配置
編輯tomcat的配置檔案server.xml,新增如下內容:
<Host name="www.xsl.com" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Context docBase="app1" path="a" reloadable="true" />
</Host>
並且將<Engine />這一行改為如下行:
<Engine name="Catalina" defaultHost="localhost" jvmRoute="TomcatA">
這裡的TomcatA要與此前的保持一致。
建立相應的目錄及其檔案
# mkdir -pv /usr/local/tomcat7/app1/a
# cd /usr/local/tomcat7/app1/a
# vim index.jsp
新增如下測試程式碼
<%@ page language="java" %>
<%@ page import="java.util.*" %>
<html>
<head>
<title>JSP test page.</title>
</head>
<body>
<% out.println("Hello,world!"); %>
</body>
</html>
其實整個內容也就是顯示"hello world"。
啟動tomcat
# service tomcat start
(5)、測試訪問http://192.168.1.103/app1/a/index.jsp
五、tomcat的session叢集
對於web伺服器叢集技術而言,最重要的環節就是如何保證多個節點間的session資訊一致性。要實現這一點,大體有二種方式:
一是將所有的session資訊放置在一臺獨立的伺服器或者資料庫中,叢集中所有的節點都可以通過這臺伺服器獲取其相關資料資訊。不過如果這臺伺服器掛了的話,那麼所有的節點都不能獲取其session資訊了,因此,最好對這臺伺服器做高可用。但是這無疑增加了企業的成本。
二是在多臺節點間實現session資訊複製,任何一個節點都儲存了所有的session資料。只要不是所有的節點都掛掉的話,那麼仍然可以響應請求,這種方式比較可靠,但是節點之間的相互複製,會增加伺服器的負載,並且會增加網路頻寬。常見的平臺或中介軟體如microsoft asp.net和IBM WAS都會提供對兩種共享方式的支援,tomcat也是這樣,但是一般採用第二種方式。
tomcat的session叢集架構
實驗前提:
nginx所在伺服器地址為:192.168.1.103
TomcatA所在伺服器地址為:172.16.1.100
TomcatB所在伺服器地址為:172.16.1.200
關閉所有伺服器上iptables和selinux
1、實驗拓撲圖如下:
本實驗中前端採用的是nginx,其實後端tomcat主機上還應該加上apache或nginx的,只不過之前已經將該過程實現了,因此,這裡就不在給出tomcat如何與nginx、apche一起集合使用的步驟了。
2、配置安裝nginx
# yum -y install nginx
編輯nginx的配置檔案/etc/nginx/nginx.conf,並新增如下內容:
# vim /etc/nginx/nginx.conf
upstream tomcat {
server 172.16.1.100:8080 weight=1 max_fails=3 fail_timeout=10s;
server 172.16.1.200:8080 weight=1 max_fails=3 fail_timeout=10s;
}
server {
listen 80;
location / {
root /htdoc/www;
index index.html;
}
location ~* \.(jsp|do)$ {
proxy_pass http://tomcat;
}
}
最後,啟動nginx服務
# service nginx start
3、配置安裝tomcatA
tomcat和jdk的安裝過程不再給出,這裡只給出修改配置檔案的步驟。
修改tomcat的配置檔案server.xml(我這裡的路徑為/usr/local/tomcat7/conf/server.xml)
將<Engine>這一行改為如下行:
<Engine name="Catalina" defaultHost="localhost" jvmRoute="TomcatA">
其實就是添加了一個jvmRoute引數。
還需要在配置檔案中新增一個Host容器
<Host name="node1.xsl.com" appbase="webapps" unpackWARs="true" autoDeploy="true" >
<Context docBase="node" path="app" reloadable="true" />
</Host>
建立相應的目錄及檔案
# mkdir /usr/local/tomcat7/webapps/node/app
# vim /usr/local/tomcat7/webapps/node/app/index.jsp
<%@ page language="java" %>
<html>
<head><title>TomcatA</title></head>
<body>
<h1><font color="red">TomcatA.magedu.com</font></h1>
<table align="centre" border="1">
<tr>
<td>Session ID</td>
<% session.setAttribute("magedu.com","magedu.com"); %>
<td><%= session.getId() %></td>
</tr>
<tr>
<td>Created on</td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
</html>
最後,在啟動tomcat服務即可
# service tomcat start
4、配置安裝tomcatB
tomcat和jdk的安裝過程不再給出,這裡只給出修改配置檔案的步驟。
修改tomcat的配置檔案server.xml(我這裡的路徑為/usr/local/tomcat7/conf/server.xml)
將<Engine>這一行改為如下行:
<Engine name="Catalina" defaultHost="localhost" jvmRoute="TomcatB">
其實就是添加了一個jvmRoute引數。
還需要在配置檔案中新增一個Host容器
<Host name="node2.xsl.com" appbase="webapps" unpackWARs="true" autoDeploy="true" >
<Context docBase="node" path="app" reloadable="true" />
</Host>
建立相應的目錄及檔案
# mkdir /usr/local/tomcat7/webapps/node/app
# vim /usr/local/tomcat7/webapps/node/app/index.jsp
<%@ page language="java" %>
<html>
<head><title>TomcatB</title></head>
<body>
<h1><font color="blue">TomcatB.magedu.com</font></h1>
<table align="centre" border="1">
<tr>
<td>Session ID</td>
<% session.setAttribute("magedu.com","magedu.com"); %>
<td><%= session.getId() %></td>
</tr>
<tr>
<td>Created on</td>
<td><%= session.getCreationTime() %></td>
</tr>
</table>
</body>
</html>
最後,在啟動tomcat服務即可
# service tomcat start
5、測試訪問nginx的負載功能是否成功
訪問http://192.168.1.103/node/app/index.jsp,顯示結果如下:
再一次重新整理訪問,顯示結果如下:
由此,nginx的負載均衡功能已經實現。注意觀察兩次訪問的結果,其session資訊是不相同的,這也就是說其中任何一臺tomcat伺服器宕機了,則該伺服器上的session資訊都會丟失。因此,還需要將session資訊保持下來,這裡我採用將兩臺tomcat伺服器上的session資訊相互進行復制來實現session保持功能。
6、tomcat的session(複製)叢集實現過程
在TomcatA和TomcatB上的server.xml檔案中,同時新增如下資訊,這段資訊新增到<Engine>下面。
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
//TomcatA address="172.16.1.100"
//TomcatB address="172.16.1.200"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
注意:將TomcatA和TomcatB上的<Receiver >中的address由"auto"改為可以接受心跳資訊的地址。這裡我將TomcatA上的address改為"172.16.1.100";TomcatB上的地址改為"172.16.1.200"。
然後在TomcatA和TomcatB上相應的應用程式目錄(即Context中的docBase目錄)下建立WEB-INF目錄,且建立一個web.xml檔案(直接將CATALINA_HOME/conf目錄下的web.xml檔案複製過來即可)。
# mkdir /usr/local/tomcat7/webapps/node/WEB-INF
# cp /usr/local/tomcat7/conf/web.xml /usr/local/tomcat7/webapps/node/WEB-INF
編輯/usr/local/tomcat7/webapps/node/WEB-INF/web.xml檔案,新增如下一行資訊:
<distributable/>
這行資訊新增到<web-app>容器下。
最後,再次訪問http://192.168.1.103/node/app/index.jsp
再次重新整理訪問,顯示結果如下:
觀察兩次訪問的SESSION ID是一樣的,由此,tomcat的session叢集已經實現了。