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 的配置文件。