一、實現無 session 集羣的負載均衡
1 、安裝 apache , resin ,可以把 apache 安裝在獨立的一臺服務器上,如果硬件資源有限,也可以與其中一臺後端 resin 裝在同一臺硬件機上。
2、
下載
mod_caucho.so
、安裝
mod_caucho.so
模塊(插件)。
mod_caucho.so
是
apaceh
與
resin
實現負載均衡的插件。把
mod_caucho.so
放於
apache
安裝目錄的
modules
目錄中。在
apache
的
httpd.conf
文件中加:
#
# mod_caucho Resin Configuration
#
LoadModule caucho_module /usr/local/apache/modules/mod_caucho.so
# 這裏 apache 安裝在 /usr/local/apache /裏,根據情況自定
ResinConfigServer 192.168.3.158 6802 # 注意這裏要用具體的IP,曾經試過, apache 和其中後端 resin 安裝在同一臺服務器上,用 localhost 會出錯,但用具體的IP時就正常了 ; 這裏是用於負載均衡的端口,一般爲 6802 。
# 這裏可以只列出其中一臺後端 resin 服務器的 IP 和端口, apache 會根據以下 ressin.conf 的配置來獲得其它後端服務器的信息。
CauchoStatus yes
<Location /caucho-request>
SetHandler caucho-request
</Location>
到此,通過地址
/caucho-status
可以檢查是否完成安裝,出現一個表格顯示一些數據就表明安裝成功了,一些錯誤可以暫時不管,因爲
resin
還沒有配好,所以會出現一些紅色的錯誤。
3
、修改
conf/resin.conf
文件
,
配置後端
resin
服務器。在
<server>
à
<http >
標籤中加以下代碼:
<cluster>
<srun server-id="a" host="192.168.3.158" port="6802" index="1"/>
<srun server-id="b" host="192.168.4.242" port="6802" index="2"/>
</cluster>
server-id: 表示服務器的名稱。 caucho_module 生成的 sessionId 會以這個名稱開頭,以區分 session 的屬主。
host: 服務器的的 IP 地址
port: 端口
另一臺 resin 服務器的 resin.conf 加同樣的代碼。
啓動 a 服務器用 : httpd.sh -server a start
啓動 b 服務器用 : httpd.sh -server b start
關閉 a 服務器用 : httpd.sh -server a stop
關閉 b 服務器用 : httpd.sh -server b stop
四、啓動後端 resin 服務器
用 httpd.sh -server a start 啓動 192.168.3.158 服務器
用 httpd.sh -server b start 啓動 192.168.4.242 服務器
注意:在 192.168.3.158 服務器只啓動 a; 在 192.168.4.242 服務器只啓動 B 。
五、查看配置的狀態。
兩臺服務器都啓動後通過 /caucho-status 查看狀態,如果正常相應的服務器會用綠色 (ok) 顯示,如果仍然有問題會以紅色 (down) 顯示。
至此,沒有實現 session 集羣的負載均衡就配置完成了。現在寫起來就這麼少,真正摸索的時候可花了不少功夫。 J
帶 session 集羣的負載均衡在以下會繼續探討,這步是最基本的,把這步完成了才能做下面的工作。
以下由官方網站介紹的用來調試負載均衡的方法:
- First, check your configuration with Resin standalone.sh. In other words, add a <http port='8080'/> and check port 8080.
- Check http://localhost/caucho-status. That will tell if mod_caucho has properly connected to the backend Resin server.
- Each srun host should be green and the mappings should match your resin.conf.
- If caucho-status fails entirely, the problem is in the mod_caucho installation and the Apache httpd.conf.
- If caucho-status shows the wrong mappings, there's something wrong with the resin.conf or the pointer to the backend server in httpd.conf.
- If caucho-status shows a red servlet runner, then Resin hasn't properly started.
- If you get a "cannot connect to servlet engine", caucho-status will show red, and Resin hasn't started properly.
- If Resin doesn't start properly, you should look at the logs in resin-3.0.x/log. You should start httpd.sh -verbose or httpd.exe -verbose to get more information.
- If Resin never shows a "srun listening to *:6802" line, it's not listening for connections from mod_caucho. You'll need to add a <srun> line.
- If
you get Resin's "file not found", the Apache configuration is good but
the resin.conf probably points to the wrong directories.
二、帶 session 集羣的負載均衡配置
前面已經配置好不帶有 session 集羣的負載均衡了,這時候我們的基本任務就已完成了。但不帶 session 集羣的負載均衡還不是最完美的,當其中一臺服務( A )停止響應而將請求交給另外一臺服務器( B )處理時,由於不能保存前一臺服務器( A )的 session ,導致所有保存在在 A 服務器 session 中的信息都會丟失,這雖然比沒有實現負載均衡前打不開網站要好很多,但我們的要求不應就此滿足,我們要實現多臺服務器之間無縫轉接,要讓客戶端不會覺察到服務器端所做的變動。這時候就需要實現 session 的集羣了(其實還可以通過數據庫的方式實現,由於隨着併發量的提高,可能會出現數據庫的瓶頸,因此我不建議使用這種方法)。 Resin 已經可以支持這一功能了。下面會介紹這種實現的原理,知道實現原理,我們做起事件來纔會事半功倍。如果暫時不想知道這些原理,只想知道如何配置,可以跳過這些原理的說明。
1 、原理說明
首先要說明點實現分佈式 session 的很基本的前提 ------ 序列化。 resin 分佈式的 session 是基於 java 序列化機制來保存 session 的,因此應用程序的對象必須要實現 java.io.Serializable 這個接口,這樣分佈式的 session 才能正常的工作。也就是說放在 session 裏的對象必須是實現了 java.io.Serializable 這個接口的。
以下說明請求 session 的兩種方式:普通的請求方式和高效的請求方式
普通的請求方式
首先,在 session 集羣中,每臺服務器會以另一臺服務器作爲它的備份服務器,當 session 的值有改變時,除了在本服務器上保存新的 session 外,還會把這些更新發送給備份服務器,,在備份服務器上也保存一份 session ,當服務器重啓後,會查找備份服務器中的 session 來更新自身的 session 。
要看清楚集羣式的 session 是如何工作的,可以看作負載均衡器是隨機的發送一個請求到後端集羣服務器中其中一個服務器的。服務器 C 擁有一個當前的 session, 但負載均衡器把請求轉發給了 A ,在以下的示圖中,這個請求將會修改這個 session 的值,因此在這裏除了要加載這個 session 之外,還要保存這個 session 的值。
session ID 會包含有當前服務器的信息,舉個例子,一個 sessionID 值爲 ca8MbyA ,分析後知道這個 session 在 C ,同時 resin 也可以通過 cookie 知道備份 session 所在的服務器, A 必須知道每一個 cookie 所反映的 session 所在在的服務器,以便跟這些主機進行通信。以上的示例的配置定義了所有 A 需要知道的服務器羣。如果 C 發生了故障, A 可以通過這個配置爲 ca8MbyA 找 D 作爲 C 的替代。
當請求過來時, A 會向 C 發出請求獲取 session 的序列化數據( 2 : load ),由於 A 不會緩存 C 的 session 的數據,針對每一個請來它必須向 C 獲取最新過 session 數據。由於請求都只是讀取數據, A 唯一要做的事情是基於 TCP 協議的加載,會跳過第 3—5 步。但如果設置了 always-save-session ,將會執行保存的動作,也就是會執行 3—5 步。
在請求的最後, A 會把 session 的所有修改提交給 C ( 3 : store )。如果設置 always-save-session 爲 false , session 並未做任何改動時,這一步會跳過。 A 把最新的序列化後的 session 內容提交給 C 。 C 把這個 session 保存到本地的硬盤 (4:save) ,並且保存到備份的 D(5 : backup) 。
高效的 session 請求方式 (sticky-Session)
實現了 sticky-session 的高效的負載均衡器會改進 session 的處理方式。拿以上的請求來說,很大的代價將會花在網絡的流量上,也就是花中第 2 ( load )、第 3 步 (store) 中。高效的負載均衡器會避開第 2 、 3 步。看以下圖示:舉個例子,一個 sessionId 爲 caaMbyA 的 session ,這個 session 是屬於 C , C 直接把 session 提供給請求的 servlet ,不需要再經過 A ,不需要任何額外的工作,也不需要佔用網絡帶寬。對於一個只讀的請求,不需要任何的 session 集羣的管理的成本。因此一個高效的負載均衡器會在這方面提高執行效率 (apache 就是採用這種方式 ) 。通常的瀏覽器會沒有任何 session 集羣的管理成本,但 AOL 瀏覽器會採用 non-sticky-session 的處理方式,也就是上面所提到的那種方式。
Session 的恢復
當 C 由於某些原因需要重啓,重啓後, C 必須要使用最高版本的 session ,但它自身保存在文件中 session 很可能已經不是最新的了。這時候會怎樣處理呢?當一個“新的” session 請求到來時, C 會從 D 加載備用 session , D 的會是最新版本的 session 。一旦最新的 session 被加載後, C 就會正常工作,不會讓人感覺到它重啓過。
2 、配置
(1) 、配置 session 集羣服務器。 session 的集羣在 <server> 這個 tag 裏配置。使用的標記是 <srun> ,這個標籤在服務器的
<cluster> 標籤裏。用如下的設置:
<server>
<cluster>
<srun id="a" host="192.168.0.1" port="6802" index="1"/>
<srun id="b" host="192.168.0.2" port="6802" index="2"/>
<srun id="c" host="192.168.0.3" port="6802" index="3"/>
<srun id="d" host="192.168.0.4" port="6802" index="4"/>
</cluster>
<persistent-store type="cluster">
<init path="cluster"/>
</persistent-store>
其實 <cluster> 就是共用之前我們設置好的配置。
(2) 、配置序列化 session 。在 <web-app> 標籤里加上以下的配置:
<web-app id='/foo'>...
<session-config><use-persistent-store/>
</session-config>
...
</web-app>
( 3 )、設置 always-save-session
分佈式的 session 必須要知道 session 什麼時候做了修改 , 以便保存新的 session 值。盡
管 resin 知道應用程序什麼時候調用了 Httpsession.setAttribute 方法,但它不清楚 session
內部的值是否已經被改變。用以下的例子說明這一點:
Counter.java
package test;
public class Counter implements java.io.Serializable {
private int _count;
public int nextCount() { return _count++; }
}
把一個 Counter 對像做爲 session 的一個 attribute , resin 不知道應用程序什麼時候去調用
nextCounte 方法,它不能知道 _count 的值是否已經有了改動, resin 不會去備份這個新的
session ,除非設置了 always-save-session ,當 always-save-session 爲 true 時, resin 會在
每一個請求時備份 session 。因此爲了保存最新的 session 值, <session-config> 的配置還需要加上 <always-save-session/> 這一行。
設置如下:
<web-app id='/foo'>
...
<session-config>
<use-persistent-store/>
<always-save-session/>
</session-config>
...
</web-app>
附:
使用 apache 與 resin 實現負載均衡的原理
當使用 apache 作爲 web 服務器時, mod_caucho.so 所做的工作是實現負載均衡.它發揮的作用是相當於硬件或 resin 的 LoadBalanceServlet 的負載均衡的作用。
要理解 resin 怎樣通過插件實現負載均衡,知道 mod_caucho.so 如何分發請求到後端 JVM 是很重要的。以下的幾點順序說明了一個典型的請求。
1、 請求到達 apache 。
2、 mod_caucho.so 驗證這個請求是否屬於由 resin 來處理。
3、 mod_caucho.so 選擇一個後端 JVM ,來處理請求。正常情況下分兩種情況:
A、如果這是個舊的 session ,會把請求發送到擁有這個 session 的 JVM 。(這是通過 cookie 裏的 sessionid 來確定去到哪一個 JVM 的)
B、如果這是一個新的請求,根據輪循的原則,把請求發送到其中一個JVM。
注:經測試後發現,如果有一箇舊的 session ,它的屬主服務器 (A) 當機,不接受請求了,這個時候插件會把這個請求轉給另一臺服務器 (B) ,在這個時候,如果 session 沒有實現集羣,實現多臺服務器之間 session 同步,那麼將會在另一臺服務器 (B) 上建立一個新的 session ,原來的登錄等信息將會丟失(但這比不能訪問網站相比,這已是很大的進步了)。這時候儘管這個 session 屬於A,但這時候請求還是會轉發給B處理。如果A又重新正常工作了,這時候原來屬於 A 的請求又會迴歸到A處理,不會再交給B處理了,這時候如果在B裏又保存有登錄信息,那麼轉移給B後,這些信息又會丟失了 ( 大家想想,假如在A發生故障,這時候請求提由B處理,幾分種後又恢復正常了,請求又交回給A處理,然後幾分鐘後A又當機了,請求又會再交由B處理,整個過程的時間在 session 超時的時間內,那麼第二把請求轉發給B處理時,是否還需登錄呢?答案是不需要的,因爲沒有 B 裏 session 沒有超時,仍然保存着上一次登錄的 session ,但如果A正常的那幾分鐘對 session 作了修改,那麼將不會反映到B裏。這是經過測試得出的結論 ) 。換句話說,正常情況下插件按照 sessionid 的值自動把請求發送給相應的服務器,在其中一臺服務器當機時,這個規則就會被打破。 sessionId 爲 AsessionIdvalue 的請求不一定只能由A處理, sessionId 爲 BsessionIdvalue 的請求不一定只能由B來處理。另外要注意的一點是,由A轉到B的時候,在交接過程中,會出現短暫的中斷,這點暫時不清楚原因,仍需要進一步研究。
4、 mod_caucho.so 通過 TCP secket 把請求發送到後端 JVM 。
5、 mod_caucho.so 接收後端發通過 TCP socket 送過來的響應。
mod_caucho.so 必須知道哪一個請求應該去到 resin ,比如, servlet-mappings 和 jsp 頁面就應該去到 resin 。並且它必須知道後端服務器的TCP連接的主機名和端口,是通過 <srun> 這個標籤獲取這些信息。/ caucho-status 這個鏈接會在一個表格裏顯示所有的信息。 mod_caucho.so 通過正在運行的 resin 服務器獲取這些信息。
如何實現通過 sessionId 來判斷某一請求是由A處理還是由B處理呢?以下介紹這個過程的實現原理。
爲了使同一 session 保留同一臺JVM上, resin 會使用服務器的編號來編碼 cookie 的 sessionId ,用上面的例子,插件會按以下的規則來生成 sessionId
index
cookie prefix
1
a xxx
2
b xxx
3
c xxx
在服務器端, mod_caucho 會解碼 cookie ,並把請求發送到正確的服務器。比如 sesionId 爲 bX8ZwooOz 的請求,將會發送到第二臺服務器。基本原理就是這樣了。