1. 程式人生 > >Tomcat負載均衡原理詳解及配置

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,可以看到如下程式碼:

View Code

根據需要更改<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 />節點,然後在節點結束後加上:
View Code

這裡的"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 Code

1)輪詢均衡策略的配置

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> 
複製程式碼