Tomcat負載均衡原理詳解及配置
結構圖
使用Tomcat的童鞋們注意了。為了增加tomcat的效能和穩定性,我們一般採用balance和session同步機制。 下圖列出了我們常用也是最簡單的解決方案。
說明
1 balance
1.1 mod_proxy方式
mod_proxy是一種分工合作的的形式,通過主伺服器跳轉到各臺主機負責不同的任務而實現任務分工,這種形式不能實現負載均衡,只能提供主伺服器的訪問跳轉
修改apache的httpd.conf檔案配置
開啟httpd.conf檔案,取消下面四行的註釋,用以開啟代理所需的.so支援模組。
View Code開啟檔案conf\extra\httpd-vhosts.conf,可以看到如下程式碼:
根據需要更改<VirtualHost>節點內的引數。
說明:NameVirtualHost *:80和<VirtualHost *:80>中 的*為當前伺服器IP,如果有固定IP可以用IP把*替換掉,我這裡使用的是動態IP,所以用*,我看apache幫助文件的時候,一直認為這裡的*為對 應的域名,這個想法應該是錯誤的,因為我填上域名的時候一直沒有設定成功。ServerName這個填域名,DocumentRoot填 ServerName上域名對應的根目錄。注:
如果訪問域名出現403許可權錯誤,且對應的資料夾everyone的許可權都是全部控制,則問題出在httpd.conf上。編輯httpd.conf,找到DocumentRoot "C:/Program Files/Apache Software Foundation/Apache2.2/htdocs"這項,這是預設根目錄路徑,但是要更改的不是這個,一直往下找,找到<Directory />節點,然後在節點結束後加上:
這裡的"C:/Program Files/Apache Software Foundation/Apache2.2/docs/dummy-host.leader89"和"C:/Program Files/Apache Software Foundation/Apache2.2/docs/dummy-host2.leader89"為前面VirtualHost裡的路徑。
儲存httpd.conf和httpd-vhosts.conf,然後重啟Apache。
然 後訪問dummy-host.leader89開啟的是C:/Program Files/Apache Software Foundation/Apache2.2/docs/dummy-host.leader89目錄,
訪問dummy-host2.leader89的是C:/Program Files/Apache Software Foundation/Apache2.2/docs/dummy-host2.leader89目錄,
實現了單IP多域名多站點的功能。
1.2 mod_proxy_blancer方式
mod_proxy_balancer是mod_proxy的擴充套件,提供負載平衡支援,通過mod_proxy_balancer.so包實現負載平衡,公司生產伺服器暫時就採用這種方式。
修改apache的httpd.conf檔案配置
開啟httpd.conf檔案,取消下面四行的註釋,用以開啟代理所需的.so支援模組。
View Code在httpd.conf檔案最後新增以下程式碼:
View Code將下載的tomcat壓縮包解壓兩份,分別命名為tomcat1、tomcat2。修改tomcat2中conf/server.xml中部分埠號(因為我在本機做測試,所以為了解決埠號被佔用的問題修改tomcat2的埠號,使tomcat1與tomcat2能夠同時啟動,實現多伺服器;如果有多臺PC伺服器可不必修改),修改內容如下:
View Code1)輪詢均衡策略的配置
ProxyPass / balancer://proxy/
<Proxy balancer://proxy> BalancerMember http://127.0.0.1:8080/
BalancerMember http://127.0.0.1:8081/
</Proxy>實現負載均衡的原理如下:
假設Apache接收到http://localhost/test請求,由於該請求滿足ProxyPass條件(其URL字首為“/"),該請求會 被分發到後臺某一個BalancerMember,譬如,該請求可能會轉發到http://127.0.0.1:8080/進行處理?當第二 個滿足條件的URL請求過來時,該請求可能會被分發到另外一臺BalancerMember,譬如,可能會轉發到 http://127.0.0.1:8081/如此迴圈反覆,便實現了負載均衡的機制?
2)按權重分配均衡策略的配置
ProxyPass / balancer://proxy/
<Proxy balancer://proxy> BalancerMember http://127.0.0.1:8080/ loadfactor=3
BalancerMember http://127.0.0.1:8081/ loadfactor=1
</Proxy>
引數"loadfactor"表示後臺伺服器負載到由Apache傳送請求的權值,該值預設為1,可以將該值設定為1到100之間的任何值?以上面 的配置為例,介紹如何實現按權重分配的負載均衡,現假設Apache收到http://myserver/test 4次這樣的請求,該請求分別被負載到後臺 伺服器,則有3次連續的這樣請求被負載到BalancerMember為http://127.0.0.1:8080/的伺服器,有1次這樣的請求被 負載BalancerMember為http://127.0.0.1:8081/後臺伺服器?實現了按照權重連續分配的均衡策略?
3)權重請求響應負載均衡策略的配置
ProxyPass / balancer://proxy/ lbmethod=bytraffic
<Proxy balancer://proxy> BalancerMember http://127.0.0.1:8080/ loadfactor=3
BalancerMember http://127.0.0.1:8081/ loadfactor=1
</Proxy>
引數“lbmethod=bytraffic"表示後臺伺服器負載請求和響應的位元組數,處理位元組數的多少是以權值的方式來表示的? “loadfactor"表示後臺伺服器處理負載請求和響應位元組數的權值,該值預設為1,可以將該值設定在1到100的任何值?根據以上配置是這麼進行均 衡負載的,假設Apache接收到http://myserver/test請求,將請求轉發給後臺伺服器,如果BalancerMember為http://127.0.0.1:8080/後臺伺服器負載到這個請求,那麼它處理請求和響應的位元組數是BalancerMember為http://127.0.0.1:8081/伺服器的3倍(回想(2)均衡配置,(2)是以請求數作為權重負載均衡的,(3)是以流量為權重負載均衡的,這是 最大的區別)?
至此配置以完成
在tomcat1中webapps資料夾下新建test專案資料夾,test目錄下新建如下頁面
View Code如需session複製功能此步驟為必須操作
開啟專案的WEB-INF下的web.xml在</web-app>內新增<distributable/>標籤,如果沒有則手動建立目錄結構
View Code將tomcat1下的test專案複製一份到tomcat2的webapps目錄下
至此所有操作已完成
啟動tomcat1、tomcat2、apache。開啟瀏覽器,輸入http://localhost/test/test.jsp回車,重新整理幾次即可從tomcat1與tomcat2的控制檯看到負載效果。輸入session即可看到session複製效果
1.3 mod_jk方案
proxy、proxy_blancer和mod_jk的比較
- proxy的缺點是,當其中一臺tomcat停止執行的時候,apache仍然會轉發請求過去,導致502閘道器錯誤。但是隻要伺服器再啟動就不存在這個問題。
- mod_jk方式的優點是,Apache 會自動檢測到停止掉的tomcat,然後不再發請求過去。
缺點就是,當停止掉的tomcat伺服器再次啟動的時候,Apache檢測不到,仍然不會轉發請求過去。 - proxy和mod_jk的共同優點是.可以只將Apache置於公網,節省公網IP地址資源。
可以通過設定來實現Apache專門負責處理靜態網頁,讓Tomcat專門負責處理jsp和servlet等動態請求。
共同缺點是:如果前置Apache代理伺服器停止執行,所有叢集服務將無法對外提供。 - proxy和mod_jk對靜態頁面請求的處理,都可以通設定來選取一個儘可能優化的效果。
mod_proxy_balancer和mod_jk都需要修改tomcat的配置檔案配合
<Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1"> - 這三種Tomcat叢集方式對實現最佳負載均衡都有一定不足,mod_proxy_balancer和mod_jk相對好些,mod_jk的設定能力更強些。lbfactor引數來分配請求任務。
- apache自帶mod_proxy功能模組中目前只可以實現兩種不同的負載均衡叢集實現方式,第一種是分工合作的的形式,通過各臺主機負責不同的任務而實 現任務分工。第二種是不同的機器在擔任同樣的任務,某臺機器出現故障主機可以自動檢測到將不會影響到客戶端,而第一種卻不能實現但第一種實現方式的優點在 於他是主伺服器負擔相應沒第二種大因為臺只是提供跳轉指路功能,形象的說他不給你帶路只是告訴你有條路可以到,但到了那是否可以看到你見的人他已經不會去管你了。相比之下第二種效能要比第一種會好很多;但他們都有個共同點都是一託N形式來完成任務的所以你的主機效能一定要好。
2 session同步
- 對於tomcat的叢集有兩種方式,這個主要是針對session而言的。一種就是sticky模式,即黏性會話模式;另外一種就是session複製模式了。
2.1 sticky模式
- 利用負載均衡器的sticky模式的方式把所有同一session的請求都發送到相同的Tomcat節點。這樣不同使用者的請求就被平均分配到叢集 中各個tomcat節點上,實現負載均衡的能力。這樣做的缺點是沒有災難恢復的能力。一旦一個節點發生故障,這個節點上所有的session資訊全部丟 失;
- 這種方式其實是由前端balancer實現的,基本不需要webServer做任何改動(只需要修改jvmRoute="tomcat1")
- 同一使用者同一session只和一個webServer互動,一旦這個webserver發生故障,本次session將丟失,使用者不能繼續使用
2.2 複製模式
- 利用Tomcat session複製的機制使得所有session在所有Tomcat節點中保持一致。當一個節點修改一個session資料的時候,該節點會把這個 session的所有內容序列化,然後廣播給所有其它節點。這樣當下一個使用者請求被負載均衡器分配到另外一個節點的時候,那個節點上有完備的 session資訊可以用來服務該請求。這種做法的問題是對session哪怕有一點點修改,也要把整個sessions資料全部序列化 (serialize),還要廣播給叢集中所有節點,不管該節點到底需不需要這個session。這樣很容易會造成大量的網路通訊,導致網路阻塞。一般採 用這種方式,當Tomcat節點超過4個時候,整個叢集的吞吐量就不能再上升了;
- 此方式是通過tomcat本身提供的功能,只需要修改server.xml檔案
(1)修改Engine節點資訊: <Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat1">
(2)去掉<Cluster> <\Cluster> 的註釋符
(3)web.xml中增加 <distributable/>
2.3 Terracotta模式
- 另一種方式就是利用開源軟體Terracotta。Terracotta的基本原理是對於叢集間共享的資料,當在一個節點發生變化的時 候,Terracotta只把變化的部分發送給Terracotta伺服器,然後由伺服器把它轉發給真正需要這個資料的節點。這樣對網路的壓力就非常小, 各個節點也不必浪費CPU時間和記憶體進行大量的序列化操作。把這種叢集間資料共享的機制應用在session同步上,相當於對tomcat第二種叢集實現 機制進行了優化,既避免了對資料庫的依賴,又能達到負載均衡和災難恢復的效果。在對比測試中,採用Terracotta搭建Tomcat叢集,節點達到8 個時候,整個叢集的吞吐量還一直是線性增長的。
2.4 三種模式比較
- sticky模式最大的缺點就是不支援failover,一旦某一個webServer發生故障則此節點上的session就會丟失,因此不建議使用。
- 複製模式可以保證個別節點發生故障不丟失session,但是複製時需要序列化資料這會影響到系統的效能。
另外效能隨著伺服器增加急劇下降,而且容易引起廣播風暴。經測試當Tomcat節點超過4個時候,整個叢集的吞吐量就不能再上升了。
需要修改server.xml和web.xml檔案 - 使用第三方軟體Terracotta進行session同步,配置對原來的web應用完全透明,原有程式不用做任何修改。。
資料不需要序列化,也不佔用webServer的記憶體,執行效率高。 - terracotta本身支援HA,增加了系統的穩定性。
- Terracotta是開源的,並且可以整合在很多主流的開源軟體中,如Jetty、Tomcat、Spring、Geronimo和EHCache等。
反向代理負載均衡 (Apache2+Tomcat7/8)
使用代理伺服器可以將請求轉發給內部的Web伺服器,讓代理伺服器將請求均勻地轉發給多臺內部Web伺服器之一上,從而達到負載均衡的目的。這種代理方式與普通的代理方式有所不同,標準代理方式是客戶使用代理訪問多個外部Web伺服器,而這種代理方式是多個客戶使用它訪問內部Web伺服器,因此也被稱為反向代理模式。
此次使用的代理為mod_proxy的方式來實現的,因為在Apache2以上的版本中已經集成了,因此不需要再另行安裝和配置了,只需要把註釋去掉即可,此去掉的配置,個人感覺與Apache虛擬機器的實現相類似,去掉以下模組的註釋:
LoadModule proxy_module modules/mod_proxy.so #提供代理伺服器功能
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so #提供負載均衡功能
LoadModule proxy_http_module modules/mod_proxy_http.so #讓代理伺服器能支援HTTP協議
然後把:
#Include conf/extra/httpd-vhosts.conf的註釋也去掉,配置的負載均衡
具體配置如下:
#虛擬機器配置,負載均衡配置 注意空格 <VirtualHost *:9999> ServerAdmin [email protected] ServerName localhost ServerAlias localhost ProxyPass / balancer://cluster/ stickysession=JSESSIONID|jsessionid nofailover=On ProxyPassReverse / balancer://cluster/ #ErrorLog "logs/error.log" #CustomLog "logs/access.log" common </VirtualHost> #The ProxyRequests directive should usually be set off when using ProxyPass. ProxyRequests Off <proxy balancer://cluster> BalancerMember ajp://localhost:8009 loadfactor=1 route=tomcat8_local smax=5 max=20 ttl=120 retry=300 timeout=15 BalancerMember ajp://192.168.1.250:8009 loadfactor=1 route=tomcat8_250 smax=5 max=20 ttl=120 retry=300 timeout=15 ProxySet lbmethod=byrequests </proxy>
其中: localhost:8009 和 192.168.1.250:8009為兩個負載均衡伺服器
loadfactor=1 route=tomcat8_local smax=5 max=20 ttl=120 retry=300 timeout=15 這個為配置的引數,最大連結,超時,等等
route=tomcat8_local 可以不寫
ProxySet lbmethod=byrequests 為實現負載均衡的方式,共有三種類型
#lbmethod=byrequests 按照請求次數均衡(預設)
#lbmethod=bytraffic 按照流量均衡
#lbmethod=bybusyness 按照繁忙程度均衡(總是分配給活躍請求數最少的伺服器)
route=tomcat8_local 根據這個route的值,分別在兩個Tomat中的Service.xml的 Engine 節點配置上jvmRoute的內容,如下: <Engine name="Catalina" defaultHost="localhost" jvmRoute="tomcat8_local">和以及jvmRoute="tomcat8_250" ,不過我在測試是,感覺如果不配置,也不會影響程式的執行。
啟動以上程式就可以進行測試了,測試頁面來源於網路:
<%@ page contentType="text/html; charset=GBK" %> <%@ page import="java.util.*" %> <html><head><title>Cluster Test</title></head> <body> <% //HttpSession session = request.getSession(true); System.out.println(session.getId()); out.println("<br> SESSION ID:" + session.getId()+"<br>"); // 如果有新的請求,則新增session屬性 String name = request.getParameter("name"); if (name != null && name.length() > 0) { String value = request.getParameter("value"); session.setAttribute(name, value); } out.print("<b>Session List:</b>"); Enumeration<String> names = session.getAttributeNames(); while (names.hasMoreElements()) { String sname = names.nextElement(); String value = session.getAttribute(sname).toString(); out.println( sname + " = " + value+"<br>"); System.out.println( sname + " = " + value); } %> <form action="testCluster.jsp" method="post"> 名稱:<input type=text size=20 name="name"> <br> 值:<input type=text size=20 name="value"> <br> <input type=submit value="提交"> </form> <b>負載均衡測試:此為:Tomcat7_a上的檔案,<font color=red>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</font><b> </body> </html>
和
<%@ page contentType="text/html; charset=GBK" %> <%@ page import="java.util.*" %> <html><head><title>Cluster Test</title></head> <body> <% //HttpSession session = request.getSession(true); System.out.println(session.getId()); out.println("<br> SESSION ID:" + session.getId()+"<br>"); // 如果有新的請求,則新增session屬性 String name = request.getParameter("name"); if (name != null && name.length() > 0) { String value = request.getParameter("value"); session.setAttribute(name, value); } out.print("<b>Session List:</b>"); Enumeration<String> names = session.getAttributeNames(); while (names.hasMoreElements()) { String sname = names.nextElement(); String value = session.getAttribute(sname).toString(); out.println( sname + " = " + value+"<br>"); System.out.println( sname + " = " + value); } %> <form action="testCluster.jsp" method="post"> 名稱:<input type=text size=20 name="name"> <br> 值:<input type=text size=20 name="value"> <br> <input type=submit value="提交"> </form> <b>負載均衡測試:此為:Tomcat7_a上的檔案,<font color=red>bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb</font><b> </body> </html>
此時訪問就會再個不同的頁面了,一個為aaaaaaaaaaaaaaaaaaaaaaaaaaaa 一個為bbbbbbbbbbbbbbbbbbbbbbbbbbb;
如下圖所示:
看到的兩個Session的值不同,因為這裡還沒有session的共享配置
注意:
以上僅僅實現了負載均衡,但是對於兩個負載同時宕機的話,就需要另外的一臺伺服器來代替了,這第n+1臺伺服器就叫熱備伺服器,配置方式與以上相同,僅需要寫上status=+H 標識即可。
以上負載均衡就算全部完成了,如果要實現Session共享,最簡單的方式就是在Tomcat中進行配置,配置如下:
在 service.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" address="auto" port="4001" 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"/> </Cluster>