Tomcat 叢集是當單臺伺服器達到效能瓶頸,通過橫向擴充套件的方式提高整體系統效能的有效手段。Nginx 是一個高效能的 HTTP 和反向代理 web 伺服器,可以通過簡單的配置實現 Tomcat 叢集的負載均衡。
本文使用的 Tomcat 是 8.5.35 版本,Nginx 是 1.14.2 版本。接下來看下配置的過程以及可能會遇到的問題,首發於微信公眾號「頓悟原始碼」。
1. 概述
對於 Web 應用來說,叢集最大的問題就是 Session 資訊的共享,一般有以下解決方法:
- 使用粘性會話,比如,使用 IP Hash 的負載均衡策略,將當前使用者的請求都集中到一臺伺服器上;缺點是單點故障,會話丟失
- 使用 Session 複製,使用 Tomcat 自帶的 Session 複製策略,將會話資訊同步到叢集的各個節點;缺點是消耗更多記憶體和頻寬,適用於小型叢集
- 使用第三方快取中介軟體快取整個叢集會話資訊,比如 Redis 快取,可由應用程式控制與 Session 的關聯,也可以適配 Tomcat
- 當然了,也可以把會話資訊存到共享檔案系統或者資料庫
在配置 Nginx 的過程中,可能會遇到以下問題:
- 配置 upstream 名稱時不能使用下劃線,比如 tomcat_ha,否則 Tomcat 會丟擲 The character [_] is never valid in a domain name 的異常
- 在 windows 上殺掉所有的 nginx.exe 程式,
taskkill /fi "imagename eq nginx.exe" /f
- 在 windows 上有個 pid 為 4 的系統程式會佔用 80 埠,所以這裡將 nginx 改為了 8000
在配置 Tomcat 叢集的過程中,需要注意的問題:
- 確保 web.xml 配置了 <distributable/> 元素
- 確保 Context 的 Manager 別被替換成了標準會話管理器
- Receiver.address 不要配置成 auto,因為預設可能會繫結 127.0.0.1;Receiver.port 可改也可不改,Tomcat 會自行檢測 4000-4100 範圍內的可用埠,自動處理衝突
- 如果在不同伺服器上,需要關閉防火牆或開埠,還有時間同步
2. Nginx 核心配置
Nginx 使用的是預設配置,新增和修改的核心配置如下:
http {
...
#gzip on;
#設定負載均衡的伺服器列表和權重
upstream tomcat-ha {
#ip_hash;
server 172.31.1.41:8080 weight=1;
server 172.31.1.42:8080 weight=1;
}
server {
listen 8000;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
#轉發請求
proxy_pass http://tomcat-ha;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
...
}
}
3. Tomcat 叢集配置
啟用叢集配置,在 <Engine> 元素中新增以下配置:
<!-- channelSendOptions=6 同步複製 -->
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="6">
<!-- 叢集 Session 管理器 -->
<Manager className="org.apache.catalina.ha.session.BackupManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"
mapSendOptions="6"/>
<!--
<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"
address="192.168.10.2"
port="5000"
selectorTimeout="100"
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.MessageDispatchInterceptor"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/>
</Channel>
<!-- 此 vavle 攔截請求,並將 Session 資訊發給內部節點 -->
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=".*\.gif|.*\.js|.*\.jpeg|.*\.jpg|.*\.png|.*\.htm|.*\.html|.*\.css|.*\.txt"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
簡單描述下工作原理:
- nginx 將請求轉發給 Tomcat1,請求登入認證,建立會話,生成 Cookie,在響應返回之前,將 Session 資訊複製到 Tomcat2
- 再次請求時,nginx 將帶著會話 Cookie 的請求轉發給了 Tomcat2,Tomcat2 發現內部 Session 池中有關聯的已認證成功的 Session 物件,不再認證返回請求資源
4. 驗證負載均衡和 Session 複製
4.1 測試環境
- 使用兩臺 PC 部署 Tomcat,對應關係是:172.31.1.41-Tomcat1,172.31.1.42-Tomcat2
- 部署基於使用 Tomcat 自帶的 SessionExample 程式,編寫了一個 tomcat-benchmark 的 web 應用
- 結合 Tomcat 自帶的 Manager 應用,檢視已部署應用內部 Session 池
4.2 負載均衡
修改 tomcat-benchmark 部署描述符檔案中的 context-param 為 "I'm Tomcat 1/2" 用於區分兩個 Tomcat,啟動 Nginx 和 Tomcat,在瀏覽器訪問 172.31.1.42:8080 可以看到請求在兩個伺服器間切換:
4.3 Session 複製
為了方便理解,這裡先把 Nginx 的負載均衡策略設定成 ip_hash:
- 假設 Nginx 始終將請求定位到 Tomcat1 上,然後在 Tomcat1 上建立會話,往會話中新增一些屬性
- 關閉 Tomcat1 模擬故障,此時 Nginx 會帶著之前的會話 Cookie 將請求轉發到 Tomcat2,上
- 檢視 Tomcat2 上是否存在與 Cookie(JSESSIONID) 關聯的 Session 資訊,若有表示複製成功
整個過程如下:
動圖正好與上述描述的相反,可以看到 Session 資訊從 Tomcat2 複製到了 Tomcat1 中。
5. 小結
搜尋微訊號「頓悟原始碼」,回覆「Tomcat」後,可獲取本文測試使用的工程以及 Nginx 和 Tomcat 的配置檔案。