Httpd+Tomcat+memcached實現session server
何為會話保持
會話保持是負載均衡最常見的問題之一,也是一個相對比較復雜的問題。會話保持有時候又叫做粘滯會話(Sticky Sessions)。會話保持是指在負載均衡器上的一種機制,可以識別客戶端與服務器之間交互過程的關連性,在作負載均衡的同時還保證一系列相關連的訪問請求會保持分配到一臺服務器上
何時需要會話保持
在討論這個問題前,我們必須先花點時間弄清楚一些概念:什麽是連接(Connection)、什麽是會話(Session),以及這二者之間的區別。需要特別強調的是,如果我們僅僅是談論負載均衡,會話和連接往往具有相同的含義。
從簡單的角度來看,如果用戶需要登錄,那麽就可以簡單的理解為會話;如果不需要登錄,那麽就是連接。
對於同一個連接中的數據包,負載均衡會將其進行NAT轉換後,轉發至後端固定的服務器進行處理。負載均衡系統內部會專門有一張表來記錄這些連接的狀況,包括:[源IP:端口]、[目的IP:端口]、[服務器IP:端口]、空閑超時時間(Idle Timeout)等等。由於負載均衡內部記錄連接狀態的這張表需要消耗系統的內存資源,因此這張表不可能無限大,所有傳統廠商都會有一定的限制。這張表的大小一般稱之為最大並發連接數,也就是系統同時能夠容納的連接數量。負載均衡的當前連接狀態表項中,設計了一個空閑超時時間(Idle Timeout)的參數。當該連接在Idle Timeout內無流量通過時,負載均衡會自動刪除該連接條目,釋放系統資源。
刪除連接後,客戶端的請求將無法保證繼續發往同一個後端服務器,需要遵循負載均衡器的流量分發策略。
在某些要求登錄狀態的情境下,要求客戶端和服務器之間保持一個會話(session)以記錄客戶端的各種信息。比如在大多數電子商務的應用系統或者需要進行用戶身份認證的在線系統中,一個客戶與服務器經常經過好幾次的交互過程才能完成一筆交易或者是一個請求的完成。由於這幾次交互過程是密切相關的,服務器在進行這些交互過程的某一個交互步驟時往往需要了解上一次或上幾次的交互過程處理結果,這就要求所有這些相關的交互過程都由一臺服務器完成,而不能被負載均衡器分散到不同的服務器上否則可能出現異常情景:
客戶端輸入了正確的用戶名和口令,但卻反復跳到登錄頁面;
用戶輸入了正確的驗證碼,但是總提示驗證碼錯誤;
客戶端放入購物車的物品丟失
…
因此會話保持機制的意義就在於,確保在合適的情境下,將來自相同客戶端的請求轉發至後端相同的服務器進行處理。換句話說,就是將客戶端與服務器之間建立的多個連接,都發送到相同的服務器進行處理。如果在客戶端和服務器之間部署了負載均衡設備,很有可能這多個連接會被轉發至不同的服務器進行處理。如果服務器之間沒有會話信息的同步機制,會導致其他服務器無法識別用戶身份,造成用戶在和應用系統發生交互時出現異常。
負載均衡希望將來自客戶端的連接、請求均衡的轉發至後端的多臺服務器,以避免單臺服務器負載過高;而會話保持機制卻要求將某些請求轉發至同一臺服務器進行處理。因此,在實際的部署環境中,我們要根據應用環境的特點,選擇適當的會話保持機制。
會話保持類型
會話保持大體可以分為三大類,session sticky,session LBcluster和session server,而這三種會話綁定方式又各有優缺點,適應不同的場景;
1 session sticky
Session sticky,即會話綁定,即將客戶端的訪問通過某種算法將它調度至固定的服務器上,而這種實現方式主要是由調度器的調度算法來實現的,像在Nginx反向代理功能中就提供了ip_hash(每個請求按訪問ip結果分配。這樣來自同一個ip的訪問將被調度到同一臺服務器上,有效解決的動態網頁存在的session共享問題。),url_hash(此方法是按照訪問url的hash結果來分配請求,使每一個url定向到同一個後端服務器,可以進一步提高後端緩存服務器的效率。Nginx本身是不支持url_hash算法的)以及更加強大的一致性hash算法。這種調度方式是基於四層的會話調度,這種調度粒度很粗。
會話綁定中一個很重要的參數就是連接超時值,負載均衡器會為每一個處於保持狀態中的會話設定一個時間值。若一個會話從上一次完成到下次再來之間的間隔時間小於超時值時,負載均衡器將會將新的連接進行會話保持;但如果這個間隔大於該超時值,負載均衡器會將新來的連接認為是新的會話然後進行負載平衡。這種會話話保持實現簡單,只需要根據數據包三四層的信息就可以實現,效率比較高。
但此種方式存在的問題就在於,當多個客戶端通過代理或地址轉換的方式訪問服務器時,由於來源地址一樣,請求都被分配到同一臺服務器上,會導致服務器之間的負載嚴重失衡。另外一種情況是,同一個客戶端產生大量並發,要求分配到多個服務器上處理的同時進行會話保持。這時基於客戶端源地址的會話保持方法也會導致負載均衡失效。以上情況出現時,就必須要考慮使用其他的會話保持方式。
2 Session Lbcluster
由於session sticky在調度中不能夠很好的實現會話的保持與高可用性,只要其中一臺主機宕機,就意味著這臺主機維持的所有會話都將丟失,這不僅對用戶是一種不好的體驗,更是一個站點的損失,於是人們開始思考能否讓後端的每個服務器都能夠攜帶所有服務器的會話呢?漸漸的找到了解決方案,那就是實現會話集群,會話集群,顧名思義,那就是將所有維持會話的服務組合成一個集群,維護該站點所有的會話信息,這樣一來,我們就不用在擔心因為某臺主機而使得用戶的信息丟失。
這種方式解決了用戶會話丟失的問題,用戶再也不會出現什麽”客戶端放入購物車的物品丟失”這類問題了。但是解決了A類問題也會帶來B類問題,每個會話服務器既要處理前端的用戶請求又要會話同步至其他的主機,如果這是一個量很大的服務站點,那麽每一臺主機在同步其他主機的會話信息,以及將自己維持的會話發送給其他服務器時將會產生大量IO操作,這就使得每個服務器的壓力變得異常大,處理前端的請求的性能大大降低。並且,同步使用的是組播的方式來實現的,大量服務器同時同步各自的會話給其他的主機,這將消耗大量的帶寬。
3 Session Server
鑒於會話集群帶來的新問題,我們選擇一組服務器來專門用戶進行會話的管理,後端服務器只需要將自己的會話寫入到後端的會話服務器即可,等到用戶的請求到來是,只需要與session server中的會話值做比較即可。那麽session Server怎樣存儲這些會話信息呢,於是有了以下幾種存儲方式:
1) 數據庫存放
Session信息存儲到數據庫表以實現不同應用服務器間Session信息的共享。此種方式適合數據庫訪問量不大的網站。
優點:實現簡單
缺點:由於數據庫服務器相對於應用服務器更難擴展且資源更為寶貴,在高並發的Web應用中,最大的性能瓶頸通常出現在數據庫服務器。因此如果將 Session存儲到數據庫表,頻繁的數據庫操作會影響業務。
2) 文件系統存放
通過文件系統(比如NFS)來實現各臺服務器間的Session共享。此種方式適合並發量不大的網站。
優點:各臺服務器只需要mount存儲Session的磁盤即可,實現較為簡單。
缺點:NFS對高並發讀寫的性能並不高,在硬盤I/O性能和網絡帶寬上存在較大瓶頸,尤其是對於Session這樣的小文件的頻繁讀寫操作。
3) Memcached存放
利用Memcached來保存Session數據,直接通過內存的方式讀取。
優點:效率高,在讀寫速度上會比存放在文件系統時快很多,而且多個服務器共用Session也更加方便,將這些服務器都配置成使用同一組memcached服務器就可以,減少了額外的工作量。
缺點:一旦宕機內存中的數據將會丟失,但對Session數據來說並不是嚴重的問題。如果網站訪問量太大、Session太多的時候memcached會將不常用的部分刪除,但是如果用戶隔離了一段時間之後繼續使用,將會發生讀取失敗的問題。
接下來用實驗來實現session綁定
首先介紹負載均衡,可用三種方式來實現,下面我介紹了常用的三種方法供參考
負載均衡集群(nt)
(1) nginx + tomcat cluster
環境:兩臺tomcat主機,一臺調度器(nginx)
首先三臺主機都實現地址解析
[root@node1 text]#vim /etc/hosts 172.18.77.7 node1 172.18.77.77 node2 172.18.77.74 diaodu
然後為了方便可以修改主機名,在tomcat主機上安裝包
yum install -y java-1.8.0-openjdk-devel yum install -y tomcat tomcat-lib tomcat-admin-webapps tomcat-webapps tomcat-docs-webapp mkdir -pv /usr/share/tomcat/webapps/test/{classes,lib,WEB-INF}
創建文件測試頁面vim /usr/share/tomcat/webapps/test/index.jsp
<%@ page language="java" %> <html> <head><title>TomcatA</title></head> <body> <h1><font color="red">TomcatA.magedu.com</font></h1> <table align="centre" border="1"> <tr> <td>Session ID</td> <% session.setAttribute("magedu.com","magedu.com"); %> <td><%= session.getId() %></td> </tr> <tr> <td>Created on</td> <td><%= session.getCreationTime() %></td> </tr> </table> </body> </html>
第二臺tomcat的測試頁面:
<%@ page language="java" %> <html> <head><title>TomcatB</title></head> <body> <h1><font color="red">TomcatB.magedu.com</font></h1> <table align="centre" border="1"> <tr> <td>Session ID</td> <% session.setAttribute("magedu.com","magedu.com"); %> <td><%= session.getId() %></td> </tr> <tr> <td>Created on</td> <td><%= session.getCreationTime() %></td> </tr> </table> </body> </html>
然後重啟tomcat服務,可先在網頁測試是否能訪問http://172.18.77.77:8080/test/
然後在調度器上編輯配置文件
vim /etc/nginx/nginx.conf upstream tcsrvs { server 172.18.77.7:8080; server 172.18.77.77:8080; } location / { proxy_pass http://tcsrvs; }
最後重啟服務在瀏覽器上測試:http://172.18.77.74/test/
或者用命令訪問
[root@diaodu ~]#for i in {1..10};do curl -s http://172.18.77.74/myapp/ |grep -i tomcat ;done <head><title>TomcatB</title></head> <h1><font color="blue">TomcatB.magedu.com</font></h1> <head><title>TomcatB</title></head> <h1><font color="red">TomcatA.magedu.com</font></h1> <head><title>TomcatB</title></head> <h1><font color="blue">TomcatB.magedu.com</font></h1> <head><title>TomcatB</title></head> <h1><font color="red">TomcatA.magedu.com</font></h1> <head><title>TomcatB</title></head> <h1><font color="blue">TomcatB.magedu.com</font></h1> <head><title>TomcatB</title></head> <h1><font color="red">TomcatA.magedu.com</font></h1> <head><title>TomcatB</title></head> <h1><font color="blue">TomcatB.magedu.com</font></h1> <head><title>TomcatB</title></head> <h1><font color="red">TomcatA.magedu.com</font></h1> <head><title>TomcatB</title></head> <h1><font color="blue">TomcatB.magedu.com</font></h1> <head><title>TomcatB</title></head> <h1><font color="red">TomcatA.magedu.com</font></h1>
上述構建nt集群實現負載均衡
構建ht集群實現負載均衡
(2) httpd(調度器) + tomcat cluster
httpd: mod_proxy, mod_proxy_http, mod_proxy_balancer(構建負載均衡模塊)
tomcat cluster:http connector
安裝httpd將nginx服務停止:
[root@diaodu ~]#vim /etc/httpd/conf.d/http-tomcat.conf <Proxy balancer://tcsrvs> BalancerMember http://172.18.77.7:8080 BalancerMember http://172.18.77.77:8080 ProxySet lbmethod=byrequests </Proxy> <VirtualHost *:80> ServerName diaodu ProxyVia On ProxyRequests Off ProxyPreserveHost On <Proxy *> Require all granted </Proxy> ProxyPass / balancer://tcsrvs/ ProxyPassReverse / balancer://tcsrvs/ <Location /> Require all granted </Location> </VirtualHost>
其他配置不變:測試 http://172.18.77.74/test/
(3) httpd + tomcat cluster
httpd: mod_proxy, mod_proxy_ajp, mod_proxy_balancer
tomcat cluster:ajp connector
可先將httpd服務關閉
該方法就是基於httpd的ajp模塊實現負載均衡,將http改為ajp,將端口號改為8009即可,其他配置不變
[root@diaodu ~]#vim /etc/httpd/conf.d/http-tomcat.conf <Proxy balancer://tcsrvs> BalancerMember ajp://172.18.77.7:8009 BalancerMember ajp://172.18.77.77:8009 ProxySet lbmethod=byrequests </Proxy> <VirtualHost *:80> ServerName diaodu ProxyVia On ProxyRequests Off ProxyPreserveHost On <Proxy *> Require all granted </Proxy> ProxyPass / balancer://tcsrvs/ ProxyPassReverse / balancer://tcsrvs/ <Location /> Require all granted </Location> </VirtualHost>
測試 http://172.18.77.74/test/
當後端tomcat服務器宕機了,會調度到另一臺主機上了
Httpd+Tomcat+memcached實現session server
1 > 環境:
安裝memcached包,開啟服務;端口:11211
兩個tomcat節點:172.18.77.7(tomcatA.magedu.com),172.18.77.77(tomcatB.magedu.com)
兩個memcached節點:172.18.77.7, 172.18.77.77
一個負載均衡節點:172.18.77.74
由於本人主機不夠多,所以將緩存服務器和tomcat服務器基於同一臺了。請見諒!!!
Clients-->172.18.77.74-->(tomcatA, tomcatB)
2 >下載jar包
memcached-session-manager項目地址,http://code.google.com/p/memcached-session-manager/, https://github.com/magro/memcached-session-manager
mkdir -pv /usr/share/tomcat/webapps/test/WEB-INF/{classes,lib}
下載如下jar文件至各tomcat節點的tomcat安裝目錄下的lib目錄中
將下列兩個javolution包放在這個目下/usr/share/tomcat/webapps/test/WEB-INF/lib/
javolution-5.4.3.1.jar
msm-javolution-serializer-2.1.1.jar
將下述三個memcached的包放在/usr/share/tomcat/lib/這個目錄下
memcached-session-manager-2.1.1.jar
memcached-session-manager-tc7-2.1.1.jar
spymemcached-2.12.3.jar
3 >Tomcat的配置
分別在兩個tomcat上的某host上定義一個用於測試的context容器,並在其中創建一個會話管理器,如下所示:
<Context path="/test" docBase="/usr/local/tomcat/webapps/test" reloadable="true"> <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager" memcachedNodes="n1:172.18.77.7:11211,n2:172.18.77.77:11211" failoverNodes="n1" requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$" transcoderFactoryClass="de.javakaffee.web.msm.serializer.javolution.JavolutionTranscoderFactory" /> </Context>
分別為兩個context提供測試頁面:
tomcatA:
# vim /usr/share/tomcat/webapps/test/index.jsp 添加如下內容: <%@ page language="java" %> <html> <head><title>TomcatA</title></head> <body> <h1><font color="red">TomcatA.magedu.com</font></h1> <table align="centre" border="1"> <tr> <td>Session ID</td> <% session.setAttribute("magedu.com","magedu.com"); %> <td><%= session.getId() %></td> </tr> <tr> <td>Created on</td> <td><%= session.getCreationTime() %></td> </tr> </table> </body> </html>
tomcatB:
# mkdir -pv /usr/share/tomcat/webapps/test/WEB-INF/{classes,lib} # vim /usr/shaaer/tomcat/webapps/test/index.jsp 添加如下內容: <%@ page language="java" %> <html> <head><title>TomcatB</title></head> <body> <h1><font color="blue">TomcatB.magedu.com</font></h1> <table align="centre" border="1"> <tr> <td>Session ID</td> <% session.setAttribute("magedu.com","magedu.com"); %> <td><%= session.getId() %></td> </tr> <tr> <td>Created on</td> <td><%= session.getCreationTime() %></td> </tr> </table> </body> </html>
4 > Http的反向代理配置
在172.18.77.74上配置反向代理的負載均衡內容
[root@diaodu ~]#vim /etc/httpd/conf.d/http-tomcat.conf <Proxy balancer://tcsrvs> BalancerMember http://172.18.77.7:8080 BalancerMember http://172.18.77.77:8080 ProxySet lbmethod=byrequests </Proxy> <VirtualHost *:80> ServerName diaodu ProxyVia On ProxyRequests Off ProxyPreserveHost On <Proxy *> Require all granted </Proxy> ProxyPass / balancer://tcsrvs/ ProxyPassReverse / balancer://tcsrvs/ <Location /> Require all granted </Location> </VirtualHost>
5 >測試:
在瀏覽器中訪問http://172.16.100.6/test,結果如下圖所示,其session ID在負載均衡環境中保持不變。
註意:上述配置均在centos 7.3版本上進行的,版本的高低可能安裝的javolution包和
memcached包不同,會出現服務啟動不了
Httpd+Tomcat+memcached實現session server