Tomcat通過Memcached實現session共享的完整部署記錄

散盡浮華發表於2017-02-14

 

對於web應用叢集的技術實現而言,最大的難點就是:如何能在叢集中的多個節點之間保持資料的一致性,會話(Session)資訊是這些資料中最重要的一塊。要實現這一點, 大體上有兩種方式:
一種是把所有Session資料放到一臺伺服器上或者資料庫中,叢集中的所有節點通過訪問這臺Session伺服器來獲取資料;
另一種就是在叢集中的所有節點間進行Session資料的同步拷貝,任何一個節點均儲存了所有的Session資料。

在叢集系統下實現session統一的有如下幾種方案:
1) 請求精確定位:session sticky,例如基於訪問ip的hash策略,即當前使用者的請求都集中定位到一臺伺服器中,這樣單臺伺服器儲存了使用者的session登入資訊,如果當機,則等同於單點部署,會丟失,會話不復制。
2) session複製共享:session replication,如tomcat自帶session共享,主要是指叢集環境下,多臺應用伺服器之間同步session,使session保持一致,對外透明。 如果其中一臺伺服器發生故障,根據負載均衡的原理,排程器會遍歷尋找可用節點,分發請求,由於session已同步,故能保證使用者的session資訊不會丟失,會話複製,。
此方案的不足之處:
必須在同一種中介軟體之間完成(如:tomcat-tomcat之間).
session複製帶來的效能損失會快速增加.特別是當session中儲存了較大的物件,而且物件變化較快時, 效能下降更加顯著,會消耗系統效能。這種特性使得web應用的水平擴充套件受到了限制。
Session內容通過廣播同步給成員,會造成網路流量瓶頸,即便是內網瓶頸。
在大併發下表現並不好
3) 基於cache DB快取的session共享
基於memcache/redis快取的session共享.即使用cacheDB存取session資訊,應用伺服器接受新請求將session資訊儲存在cache DB中,當應用伺服器發生故障時,排程器會遍歷尋找可用節點,分發請求,當應用伺服器發現session不在本機記憶體時,則去cacheDB中查詢,如果找到則複製到本機,這樣實現session共享和高可用。

Tomcat叢集session同步方案有以下幾種方式:
1)使用tomcat自帶的cluster方式,多個tomcat間自動實時複製session資訊,配置起來很簡單。但這個方案的效率比較低,在大併發下表現並不好。
2)利用nginx的基於訪問ip的hash路由策略,保證訪問的ip始終被路由到同一個tomcat上,這個配置更簡單。每個請求按訪問ip的hash結果分配,這樣每個訪客固定訪問一個後端伺服器,可以解決session(並不是共享session解決)的問題! 並且如果應用是某一個區域網大量使用者同時登入,這樣負載均衡就沒什麼作用了。
3)利用nginx外掛實現tomcat叢集和session同步,nginx-upstream-jvm-route是一個Nginx的擴充套件模組,用來實現基於Cookie的Session Sticky的功能。但是遺憾的是,這個模組的補丁在nginx1.4版本之後就沒有再更新了,所以nginx1.4之後版本跟該模組就不相容了!!  
4)利用memcached實現(MSM工具)。memcached儲存session,並把多個tomcat的session集中管理,前端在利用nginx負載均衡和動靜態資源分離,在兼顧系統水平擴充套件的同時又能保證較高的效能。即通過MSM工具把Tomcat的Session序列化後儲存到Memcached裡面,從而實現Session共享.
5)利用redis實現。使用redis不僅僅可以將快取的session持久化,還因為它支援的單個物件比較大,而且資料型別豐富,不只是快取 session,還可以做其他用途,可以一舉幾得。Redis這種方式目前還不支援Tomcat8環境(現在網上外掛不支援tomcat8,非要支援tomcat8,則需修改外掛jar包的原始碼)!
6)利用filter方法實現。這種方法比較推薦,因為它的伺服器使用範圍比較多,不僅限於tomcat ,而且實現的原理比較簡單容易控制。
7)利用terracotta伺服器共享session。這種方式配置比較複雜。

在Tomcat叢集中,當一個節點出現故障,雖然有高可用叢集來負責故障轉移,但使用者的session資訊如何保持呢?
下面介紹第4種方案,session複製同步使用MSM(Memcache-Session-Manager),即利用MSM+Memcached做Session共享。

MSM介紹:(詳細介紹可以參考:http://www.cnblogs.com/kevingrace/p/6401025.html
MSM是一個高可用的Tomcat Session共享解決方案,除了可以從本機記憶體快速讀取Session資訊(僅針對黏性Session)外,還可使用Memcached存取Session,以實現高可用。
傳統tomcat叢集,會話複製隨著結點數增多,擴充套件性成為瓶頸。MSM使用memcached完成統一管理tomcat會話,避免tomcat結點間過多會話複製。
MSM利用Value(Tomcat 閥)對Request進行跟蹤。Request請求到來時,從memcached載入session,Request請求結束時,將tomcat session更新至memcached,以達到session共享之目的, 支援sticky和non-sticky模式:
sticky : 會話粘連模式(黏性session)。客戶端在一臺tomcat例項上完成登入後,以後的請求均會根據IP直接繫結到該tomcat例項。
no-sticky:會話非粘連模式(非粘性session)。客戶端的請求是隨機分發,多臺tomcat例項均會收到請求。

在進行環境部署之前,要對cookie和session的工作機制非常瞭解,如果不瞭解其中的原理且只是機械性地去按照參考文件部署,那麼這是毫無意義的。
a)cookie是怎麼工作的?
加入我們建立了一個名字為login的Cookie來包含訪問者的資訊,建立Cookie時,伺服器端的Header如下面所示,這裡假設訪問者的註冊名是“wangshibo”,同時還對所建立的Cookie的屬性如path、domain、expires等進行了指定。

Set-Cookie:login=wangshibo;path=/;domain=msn.com; 
expires=Monday,01-Mar-99 00:00:01 GMT 

上面這個Header會自動在瀏覽器端計算機的Cookie檔案中新增一條記錄。瀏覽器將變數名為“login”的Cookie賦值為“wangshibo”。
注意,在實際傳遞過程中這個Cookie的值是經過了URLEncode方法的URL編碼操作的。 這個含有Cookie值的HTTP Header被儲存到瀏覽器的Cookie檔案後,Header就通知瀏覽器將Cookie通過請求以忽略路徑的方式返回到伺服器,完成瀏覽器的認證操作。
此外,我們使用了Cookie的一些屬性來限定該Cookie的使用。例如Domain屬效能夠在瀏覽器端對Cookie傳送進行限定,具體到上面的例子,該Cookie只能傳到指定的伺服器上,而決不會跑到其他的Web站點上去。Expires屬性則指定了該Cookie儲存的時間期限,例如上面的Cookie在瀏覽器上只儲存到1999年3月1日1秒。 當然,如果瀏覽器上Cookie太多,超過了系統所允許的範圍,瀏覽器將自動對它進行刪除。至於屬性Path,用來指定Cookie將被髮送到伺服器的哪一個目錄路徑下。
說明:瀏覽器建立了一個Cookie後,對於每一個針對該網站的請求,都會在Header中帶著這個Cookie;不過,對於其他網站的請求Cookie是絕對不會跟著傳送的。而且瀏覽器會這樣一直髮送,直到Cookie過期為止。

b)session是如何工作的?
由於http是無狀態的協議,你訪問了頁面A,然後再訪問B頁面,http無法確定這2個訪問來自一個人,因此要用cookie或session來跟蹤使用者,根據授權和使用者身份來 顯示不同的頁面。比如使用者A登陸了,那麼能看到自己的個人資訊,而B沒登陸,無法看到個人資訊。還有A可能在購物,把商品放入購物車,此時B也有這個過程, 你無法確定A,B的身份和購物資訊,所以需要一個session ID來維持這個過程。
cookie是伺服器發給客戶端並保持在客戶端的一個檔案,裡面包含了使用者的訪問資訊(賬戶密碼等),可以手動刪除或設定有效期,在下次訪問的時候,會返給伺服器。
注意:cookie可以被禁用,所以要想其他辦法,這就是session。cookie資料存放在客戶的瀏覽器上,session資料放在伺服器上。cookie同時也是session id的載體,cookie儲存session id。另外:cookie不是很安全,別人可以分析存放在本地的cookie並進行cookie欺騙,考慮到安全應當使用session。session是伺服器端快取,cookie是客戶端快取。所以建議:將登陸資訊等重要資訊存放為session;其他資訊如果需要保留,可以放在cookie中,
比如:你去商場購物,商場會給你辦一張會員卡,下次你來出示該卡,會有打折優惠,該卡可以自己儲存(cookie),或是商場代為保管,由於會員太多,個人需要儲存卡號資訊(session ID)。

為什麼要持久化session(共享session)?
因為:在客戶端每個使用者的Session物件存在Servlet容器中,如果Tomcat伺服器重啟或者當機的話,那麼該session就會丟失,而客戶端的操作會由於session丟失而造成資料丟失;如果當前使用者訪問量巨大,每個使用者的Session裡存放大量資料的話,那麼就很佔用伺服器大量的記憶體,進而致使伺服器效能受到影響。
可以使用資料庫持久化session,分為物理資料庫和記憶體資料庫。物理資料庫備份session,由於其效能原因,不推薦;記憶體資料庫可以使用redis和memcached,這裡介紹memcached的方法。

MSM工作原理:
a)Sticky Session(黏性) 模式下的工作原理:
Tomcat本地Session為主Session,Memcached 中的Session為備Session。Request請求到來時, 從memcached載入備 Session到 tomcat (僅當tomcat jvmroute發生變化時, 否則直接取Tomcat Session);Request請求結束時,將Tomcat Session更新至memcached,以達到主備同步之目的。 安裝在Tomcat上的MSM使用本機記憶體儲存Session,當一個請求執行完畢之後,如果對應的Session在本地不存在(即某使用者的第一次請求),則將該Session複製一份至Memcached;當該Session的下一個請求到達時,會使用Tomcat的本地Session,請求處理結束之後,Session的變化會同步更新到 Memcached,保證資料一致。當叢集中的一個Tomcat掛掉,下一次請求會被路由到其他Tomcat上。負責處理此此請求的Tomcat並不清楚Session資訊,於是從Memcached查詢該Session,更新該Session並將其儲存至本機。此次請求結束,Session被修改,送回Memcached備份。

b)Non-sticky Session (非黏性)模式下的工作原理(記住:多臺tomcat叢集或多個tomcat例項時需要選擇Non-Sticky模式,即sticky="false")
Tomcat本地Session為中轉Session,Memcached1為主Session,Memcached2為備Session。Request請求到來時,從Memcached2載入備Session到tomcat,(當容器中還是沒有Session 則從Memcached1載入主Session到tomcat,這種情況是隻有一個memcached節點,或者有Memcached1 出錯時),Request請求結束時,將Tomcat Session更新至主Memcached1和備memcached2,並且清除Tomcat Session 。以達到主備同步之目的。 多臺tomcat叢集時 需要選擇Non-Sticky模式,即sticky="false"

===================================================
Tomcat8+Memcached+Nginx實現session會話共享的操作記錄

1)基礎環境

ip                 主機名           應用                       埠
192.168.10.200     Nginx-node       nginx1.12.2                80
192.168.10.201     Tomcat-node1     java8.131、tomcat8.0.53    8080
192.168.10.202     Tomcat-node2     java8.131、tomcat8.0.53    8080
192.168.10.203     Mem-node1        memcached-1.4.34           11211
192.168.10.205     Mem-node2        memcached-1.4.34           11211

這裡的兩臺memcache,基於tomcat做會話同步;(只對動態內容快取,用於追蹤使用者會話)

使用Nginx+Tomcat進行負載均衡時,一般使用輪詢方式進行負載。但是如果使用輪詢方式的話,可能會訪問不同的Tomcat,
此時如果不進行Session共享,則相當於是一個新的Session。就比如現有系統都是需要認證登入的系統,如果沒有Session共享,則會導致使用者退出登入。
  
Nginx配置中可以使用ip_hash的方式簡單實現session共享.但是這種方式只能將session固定到單臺tomcat機器上,如果這臺tomcat機器掛掉,則session
資訊就會丟失,不能實現session的故障轉移.

下面操作在五臺機器上同樣執行:
[root@Nginx-node ~]# cat /etc/redhat-release
CentOS release 6.9 (Final)
   
為了方便測試,關閉iptables防火牆和selinux。如果是生產環境,開啟iptables後,需要開放對應的應用埠。
[root@Nginx-node ~]# setenforce 0
[root@Nginx-node ~]# getenforce
disabled
[root@Nginx-node ~]# cat /etc/sysconfig/selinux |grep "SELINUX=disabled"
SELINUX=disabled
[root@Nginx-node ~]# /etc/init.d/iptables stop
   
本案例環境部署中所需的軟體下載地址:https://pan.baidu.com/s/1E92JgSov5IqHsY9wAzgRMA
提取密碼:hwpw
下載到伺服器上的/usr/local/src目錄下.另外:節點伺服器的系統時間一定要保持一致!!
 
memcached-session-manager環境部署所需的各種jar包的下載地址:
https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration

溫馨提示:
Tomcat+MSM環境的部署對版本要求極其謹慎,本案例中下載的軟體版本是經過驗證後的正確版本,不可隨意更改這些軟體的版本,否則會導致環境部署失敗!

實驗拓撲圖:

2)安裝tomcat(在192.168.10.201和192.168.10.202兩臺機器上操作)

安裝java8環境。先解除安裝掉系統自帶的java7,然後安裝java8
[root@Tomcat-node1 ~]# java -version
java version "1.7.0_131"
OpenJDK Runtime Environment (rhel-2.6.9.0.el6_8-x86_64 u131-b00)
OpenJDK 64-Bit Server VM (build 24.131-b00, mixed mode)
[root@Tomcat-node1 ~]# yum -y remove java-1.7.0-openjdk*
[root@Tomcat-node1 ~]# yum -y remove tzdata-java.noarch
[root@Tomcat-node1 ~]# java -version                  
-bash: /usr/bin/java: No such file or directory
  
[root@Tomcat-node1 ~]# ll /usr/local/src/jdk-8u131-linux-x64_.rpm
-rw-rw-r--. 1 root root 169983496 Nov 19  2017 /usr/local/src/jdk-8u131-linux-x64_.rpm
[root@Tomcat-node1 ~]# rpm -ivh /usr/local/src/jdk-8u131-linux-x64_.rpm --force
[root@Tomcat-node1 ~]# vim /etc/profile
......
JAVA_HOME=/usr/java/jdk1.8.0_131
JAVA_BIN=/usr/java/jdk1.8.0_131/bin
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/bin:/sbin/
CLASSPATH=.:/lib/dt.jar:/lib/tools.jar
export JAVA_HOME JAVA_BIN PATH CLASSPATH
  
[root@Tomcat-node1 ~]# source /etc/profile
[root@Tomcat-node1 ~]# java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
You have new mail in /var/spool/mail/root
  
安裝配置tomcat8
[root@Tomcat-node1 ~]# cd /usr/local/src/
[root@Tomcat-node1 src]# ll apache-tomcat-8.0.53.tar.gz
-rw-rw-r--. 1 root root 9472492 Nov  9  2017 apache-tomcat-8.0.53.tar.gz
[root@Tomcat-node1 src]# tar -zvxf apache-tomcat-8.0.53.tar.gz
[root@Tomcat-node1 src]# mv apache-tomcat-8.0.53 /usr/local/tomcat8
  
啟動tomcat
[root@Tomcat-node1 src]# /usr/local/tomcat8/bin/startup.sh
Using CATALINA_BASE:   /usr/local/tomcat8
Using CATALINA_HOME:   /usr/local/tomcat8
Using CATALINA_TMPDIR: /usr/local/tomcat8/temp
Using JRE_HOME:        /usr/java/jdk1.8.0_131
Using CLASSPATH:       /usr/local/tomcat8/bin/bootstrap.jar:/usr/local/tomcat8/bin/tomcat-juli.jar
Tomcat started.
You have new mail in /var/spool/mail/root
[root@Tomcat-node1 src]# ps -ef|grep tomcat
root      8477     1 87 03:11 pts/0    00:00:03 /usr/java/jdk1.8.0_131/bin/java -Djava.util.logging.config.file=/usr/local/tomcat8/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -classpath /usr/local/tomcat8/bin/bootstrap.jar:/usr/local/tomcat8/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat8 -Dcatalina.home=/usr/local/tomcat8 -Djava.io.tmpdir=/usr/local/tomcat8/temp org.apache.catalina.startup.Bootstrap start
root      8528  6829  0 03:11 pts/0    00:00:00 grep tomcat
[root@Tomcat-node1 src]# lsof -i:8080
COMMAND  PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
java    8477 root   49u  IPv6 12974768      0t0  TCP *:webcache (LISTEN)

再另一臺tomcat節點192.168.10.202如上同樣部署。

編寫一個測試頁面(直接修改index.jsp檔案):
[root@Tomcat-node1 ~]# cat /usr/local/tomcat8/webapps/ROOT/index.jsp 
<html>
    <body bgcolor="green">      
    <center>     
    <%=  request.getSession().getId()  %>     
    <h1>192.168.10.201</h1> 
    <h1>port:8080</h1>
    <h1>this is Tomcat-node1 ! </h1>  
    </center>
    </body>
</html>

<%@ page contentType="text/html;charset=UTF-8" isELIgnored="false"%>
SessionID:<%=session.getId()%><BR>
SessionIP:<%=request.getServerName()%> <BR>
SessionPort:<%=request.getServerPort()%>
<%     out.println("This is Tomcat server 201 !");     %>

另一臺tomcat的測試頁面為:
[root@Tomcat-node2 ~]# cat /usr/local/tomcat8/webapps/ROOT/index.jsp 
<html>
    <body bgcolor="green">      
    <center>     
    <%=  request.getSession().getId()  %>     
    <h1>192.168.10.202</h1> 
    <h1>port:8080</h1>
    <h1>this is Tomcat-node2! </h1>  
    </center>
    </body>
</html>

<%@ page contentType="text/html;charset=UTF-8" isELIgnored="false"%>
SessionID:<%=session.getId()%><BR>
SessionIP:<%=request.getServerName()%> <BR>
SessionPort:<%=request.getServerPort()%>
<%     out.println("This is Tomcat server 202 !");     %>

3)安裝Nginx(在192.168.10.200機器上操作)

[root@Nginx-node ~]# cd /usr/local/src/
[root@Nginx-node src]# ll
total 8920
-rw-rw-r--. 1 root root  981687 Oct 27  2017 nginx-1.12.2.tar.gz
-rw-rw-r--. 1 root root 5453234 Aug 23  2018 openssl-1.1.0i.tar.gz
-rw-rw-r--. 1 root root 2081413 Aug 23  2018 pcre-8.42.tar.gz
-rw-rw-r--. 1 root root  607698 Jan 16  2017 zlib-1.2.11.tar.gz

安裝依賴包
[root@Nginx-node src]# yum -y install gcc gcc-c++

安裝pcre庫
[root@Nginx-node src]# tar -zvxf pcre-8.42.tar.gz
[root@Nginx-node src]# cd pcre-8.42
[root@Nginx-node pcre-8.42]# ./configure && make && make install

安裝zlib庫
[root@Nginx-node pcre-8.42]# cd /usr/local/src/
[root@Nginx-node src]# tar -zvxf zlib-1.2.11.tar.gz 
[root@Nginx-node src]# cd zlib-1.2.11
[root@Nginx-node zlib-1.2.11]# ./configure && make && make install

安裝openssl
[root@Nginx-node zlib-1.2.11]# cd /usr/local/src/
[root@Nginx-node src]# tar -zvxf openssl-1.1.0i.tar.gz 
[root@Nginx-node src]# cd openssl-1.1.0i
[root@Nginx-node openssl-1.1.0i]# ./config && make && make install

安裝nginx,特別注意要指定prce zlib openssl原碼包位置
[root@Nginx-node openssl-1.1.0i]# cd /usr/local/src/
[root@Nginx-node src]# tar -zvxf nginx-1.12.2.tar.gz 
[root@Nginx-node src]# cd nginx-1.12.2                 
[root@Nginx-node nginx-1.12.2]# ./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-pcre=/usr/local/src/pcre-8.42 --with-zlib=/usr/local/src/zlib-1.2.11  --with-openssl=/usr/local/src/openssl-1.1.0i
[root@Nginx-node nginx-1.12.2]# make && make install

安裝成功後配置nginx
[root@Nginx-node nginx-1.12.2]# cd /usr/local/nginx/conf/
[root@Nginx-node conf]# cp nginx.conf nginx.conf.bak
[root@Nginx-node conf]# cat nginx.conf
#user  nobody;
worker_processes  8;
 
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
 
#pid        logs/nginx.pid;
 
worker_rlimit_nofile 65535;
events {
   use epoll;
    worker_connections  65535;
}
 
http {
    include       mime.types;
    default_type  application/octet-stream;
    charset utf-8;
         
    ######
    ## set access log format
    ######
    log_format  main  '$http_x_forwarded_for $remote_addr $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_cookie" $host $request_time';
 
    #######
    ## http setting
    #######
    sendfile       on;
    tcp_nopush     on;
    tcp_nodelay    on;
    keepalive_timeout  65;
    fastcgi_connect_timeout 30000;
    fastcgi_send_timeout 30000;
    fastcgi_read_timeout 30000;
    fastcgi_buffer_size 256k;
    fastcgi_buffers 8 256k;
    fastcgi_busy_buffers_size 256k;
    fastcgi_temp_file_write_size 256k;
    fastcgi_intercept_errors on;
 
    ##cache##
    client_header_timeout 60s;
    client_body_timeout 60s;
    client_max_body_size 10m;
    client_body_buffer_size 1m;
    proxy_connect_timeout 5;
    proxy_read_timeout 60;
    proxy_send_timeout 5;              
    proxy_buffer_size 64k;
    proxy_buffers 4 128k;
    proxy_busy_buffers_size 128k;
    proxy_temp_file_write_size 1m;
    proxy_temp_path /home/temp_dir;
    proxy_cache_path /home/cache levels=1:2 keys_zone=cache_one:200m inactive=1d max_size=30g;
    ##end##
 
    gzip  on;
    gzip_min_length  1k;
    gzip_buffers     4 16k;
    gzip_http_version 1.1;
    gzip_comp_level 9;
    gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php;
    gzip_vary on;
 
    ## includes vhosts
    include vhosts/*.conf;
}

[root@Nginx-node conf]# mkdir vhosts
[root@Nginx-node conf]# cd vhosts/
[root@Nginx-node vhosts]# vim lb_tomcat.conf
  upstream tomcat-lb {
      server 192.168.10.201:8080;
      server 192.168.10.202:8080;
      }
                   
      server {
      listen  80;
      server_name www.kevin.com;
      location / {
          proxy_pass http://tomcat-lb;
          proxy_set_header  X-Real-IP $remote_addr;
          proxy_set_header  REMOTE-HOST $remote_addr;
          proxy_set_header  Host $host;
          proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
        }
        location ~ .*\.(gif|jpg|png|htm|html|css|ico|flv|swf)(.*) {
              proxy_pass http://tomcat-lb;
              proxy_redirect off;
              proxy_set_header Host $host;
              proxy_cache cache_one;
              proxy_cache_valid 200 302 1h;
              proxy_cache_valid 301 1d;
              proxy_cache_valid any 10m;
              expires 30d;
              proxy_cache_key $host$uri$is_args$args;
        }
}

[root@Nginx-node vhosts]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@Nginx-node conf]# /usr/local/nginx/sbin/nginx 
[root@Nginx-node conf]# lsof -i:80
COMMAND   PID   USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
nginx   25292   root    6u  IPv4 19679665      0t0  TCP *:http (LISTEN)
nginx   25293 nobody    6u  IPv4 19679665      0t0  TCP *:http (LISTEN)
nginx   25294 nobody    6u  IPv4 19679665      0t0  TCP *:http (LISTEN)
nginx   25295 nobody    6u  IPv4 19679665      0t0  TCP *:http (LISTEN)
nginx   25296 nobody    6u  IPv4 19679665      0t0  TCP *:http (LISTEN)
nginx   25297 nobody    6u  IPv4 19679665      0t0  TCP *:http (LISTEN)
nginx   25298 nobody    6u  IPv4 19679665      0t0  TCP *:http (LISTEN)
nginx   25299 nobody    6u  IPv4 19679665      0t0  TCP *:http (LISTEN)
nginx   25300 nobody    6u  IPv4 19679665      0t0  TCP *:http (LISTEN)

將域名www.kevin.com解析到192.168.10.200上,訪問http://www.kevin.com,發現訪問請求結果會負載到192.168.10.201和192.168.10.202的tomcat上了。

 

如上,在配置memcached-session-manager會話共享之前,訪問http://www.kevin.com的請求會輪詢負載到tomcat-node1和tomcat-node2兩個節點上,並且session id會隨著頁面的重新整理而改變,即此時還沒有實現session會話共享!!

4)安裝Memcached(在192.168.10.203和192.168.10.205機器上操作)
Memcached是一款免費、開源、分散式的記憶體物件快取系統, 用於減少資料庫的負載, 加快web應用程式的訪問. Memcached簡單並且強大, 其簡單的設計加快了部署, 易於開發, 快取解決了面臨的大量資料時很多的問題.

[root@mem-node1 ~]# yum -y install libevent libevent-devel
[root@mem-node1 ~]# cd /usr/local/src/
[root@mem-node1 src]# ll memcached-1.4.34.tar.gz                     
-rw-r--r-- 1 root root 391131 Jun 27 07:41 memcached-1.4.34.tar.gz
[root@mem-node1 src]# tar -zvxf memcached-1.4.34.tar.gz
[root@mem-node1 src]# cd memcached-1.4.34                 
[root@mem-node1 memcached-1.4.34]# ./configure --prefix=/usr/local/memcached
[root@mem-node1 memcached-1.4.34]# make && make install
  
啟動memcached,埠11211可以根據自己需要修改不同埠
[root@mem-node1 ~]# /usr/local/memcached/bin/memcached -d -m 512 -u root -p 11211 -c 1024 -P /var/lib/memcached.11211pid
  
檢視memcached程式是否起來
[root@mem-node1 ~]# ps -ef|grep memcached
root      1340     1  0 14:34 ?        00:00:00 /usr/local/memcached/bin/memcached -d -m 512 -u root -p 11211 -c 1024 -P /var/lib/memcached.11211pid
root      1400 16303  0 14:35 pts/0    00:00:00 grep memcached
[root@mem-node1 ~]# lsof -i:11211
COMMAND    PID USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
memcached 1340 root   26u  IPv4 18958545      0t0  TCP *:memcache (LISTEN)
memcached 1340 root   27u  IPv6 18958546      0t0  TCP *:memcache (LISTEN)
memcached 1340 root   28u  IPv4 18958549      0t0  UDP *:memcache
memcached 1340 root   29u  IPv4 18958549      0t0  UDP *:memcache
memcached 1340 root   30u  IPv4 18958549      0t0  UDP *:memcache
memcached 1340 root   31u  IPv4 18958549      0t0  UDP *:memcache
memcached 1340 root   32u  IPv6 18958550      0t0  UDP *:memcache
memcached 1340 root   33u  IPv6 18958550      0t0  UDP *:memcache
memcached 1340 root   34u  IPv6 18958550      0t0  UDP *:memcache
memcached 1340 root   35u  IPv6 18958550      0t0  UDP *:memcache
  
測試一下memcached連線,如下說明成功(輸入quit退出)
[root@mem-node1 ~]# telnet 192.168.10.203 11211
Trying 192.168.10.203...
Connected to 192.168.10.203.
Escape character is '^]'.

5)配置Tomcat,通過MSM實現共享session(192.168.10.201和192.168.10.202機器上操作)
MSM(memcached session manager), MSM是一款實現Tomcat會話保持的管理元件, 支援粘性和無粘性的配置, 目前可以在Tomcat6,7,8中使用, 並且支援Memcached會話故障轉移.提前下載MSM的類庫檔案到/usr/local/src目錄下,下載地址

[root@Tomcat-node1 ~]# cd /usr/local/src/MSM_Software
[root@Tomcat-node1 MSM_Software]# ll
total 1212
-rw-rw-r--. 1 root root  53259 Aug 27 09:53 asm-5.2.jar
-rw-rw-r--. 1 root root 323740 Aug 27 09:51 kryo-4.0.0.jar
-rw-rw-r--. 1 root root  85217 Aug 27 09:51 kryo-serializers-0.38.jar
-rw-rw-r--. 1 root root 152401 Aug 27 09:49 memcached-session-manager-1.9.7.jar
-rw-rw-r--. 1 root root  10788 Aug 27 09:49 memcached-session-manager-tc8-1.9.7.jar
-rw-rw-r--. 1 root root   5711 Aug 27 09:52 minlog-1.3.0.jar
-rw-rw-r--. 1 root root  37160 Aug 27 09:51 msm-kryo-serializer-1.9.7.jar
-rw-rw-r--. 1 root root  51287 Aug 27 09:53 objenesis-2.4.jar
-rw-rw-r--. 1 root root  20883 Aug 27 09:52 reflectasm-1.11.3.jar
-rw-rw-r--. 1 root root 472838 Aug 27 09:50 spymemcached-2.12.2.jar

特別注意:
memcached-session-manager-tc8-1.9.7.jar中的tc8為tomcat的版本號。
一定要注意:不同版本號的tomcat,對應的msm包也不同。此處為tomcat8的jar包。

需要把上面這些MSM依賴的jar包下載後全部上傳到兩臺機器的tomcat安裝路徑的lib/ 目錄下
[root@Tomcat-node1 MSM_Software]# \cp -rf /usr/local/src/MSM_Software/* /usr/local/tomcat8/lib/

接下來進行序列化tomcat配置,序列化tomcat配置的方法有很多種:
java預設序列化tomcat配置、javolution序列化tomcat配置、xstream序列化tomcat配置、flexjson序列化tomcat配置和kryo序列化tomcat配置。
官網介紹說 使用kryo序列化tomcat的效率最高,所以這裡只介紹kryo序列化。
  
在No-Stick模式和Stick模式下context.xml檔案配置也有所不同(一般用的是No-Stick模式)
只需要修改conf/context.xml檔案:
[root@Tomcat-node1 ~]# cd /usr/local/tomcat8/conf/
[root@Tomcat-node1 conf]# cp context.xml context.xml.bak


a)No-Stick模式
記住:多個tomcat例項時 需要選擇Non-Sticky模式,即sticky="false"
[root@Tomcat-node1 conf]# vim context.xml                       #在<Context>和</Context>之間新增下面內容.就在底部</Context>之前新增就行
.......
    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
         memcachedNodes="n1:192.168.10.203:11211,n2:192.168.10.205:11211"
         lockingMode="auto"
         sticky="false"
         sessionBackupAsync="false"
         sessionBackupTimeout= "1000"   
         copyCollectionsForSerialization="true"
         requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
         transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
    />

第一臺tomcat節點的congtext.xml配置好之後,再將該檔案拷貝到另一臺tomcat節點的相同路徑下


b) Stick模式。
故障轉移配置節點(failoverNodes),不能使用在Non-Sticky模式,多個使用空格或逗號分開,配置某個節點為備份節點。
當其他節點都不可用時才會儲存到備份節點,適用於sticky模式(即一臺tomcat,多臺memcached)。
[root@Tomcat-node1 conf]# vim context.xml                
......
    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
             memcachedNodes="n1:192.168.10.203:11211,n2:192.168.10.205:11211"            #多個memcached之間用空格或逗號隔開都可以的
             sticky="true"
             failoverNodes="n2"                                            
             requestUriIgnorePattern=".*\.(png|gif|jpg|css|js|swf|flv)$"
             transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
             copyCollectionsForSerialization="true"
    />

第一臺tomcat節點的congtext.xml配置好之後,再將該檔案拷貝到另一臺tomcat節點的相同路徑下,並將failoverNodes後面的引數改為n1
 
配置好之後,一定要記得重啟兩臺機器的tomcat服務!
[root@Tomcat-node1 ~]# /usr/local/tomcat8/bin/shutdown.sh      #或者直接使用kill殺死
[root@Tomcat-node1 ~]# lsof -i:8080
[root@Tomcat-node1 ~]# /usr/local/tomcat8/bin/startup.sh


======================================================================================
Manager 各引數說明:
memcachedNodes     必選項,memcached的節點資訊,多個memcached節點,中間需要使用空格
 
failoverNodes="n2"  表示當前session保持到n1的memcached節點上
failoverNodes      可選項,不能使用在non-sticky sessions模式。故障轉移配置節點,多個使用空格或逗號分開,配置某個節點為備份節點,
當其他節點都不可用時才會儲存到備份節點,官方建議配置為和tomcat同伺服器的節點。
理由如下:
假如有兩臺伺服器m1,m2,其中m1部署tomcat和memcached節點n1,m2部署memcached節點n2。
如果配置tomcat的failoverNodes值為n2或者不配置,則當伺服器m1掛掉後n1和tomcat中儲存的session會丟失,而n2中未儲存或者只儲存了部分session,
這就造成 部分使用者狀態丟失。
如果配置tomcat的failoverNodes值為n1,則當m1掛掉後因為n2中儲存了所有的session,所以重啟tomcat的時候使用者狀態不會丟失。
為什麼n2中儲存了所有的session? 因為failoverNodes配置的值是n1,只有當n2節點不可用時才會把session儲存到n1,所以這個時候n1中是沒有儲存任何session的。
 
lockingMode  可選值,預設none,只對non-sticky有效。
 
requestUriIgnorePattern  可選值,制定忽略那些請求的session操作,一般制定靜態資源如css,js一類的。
 
sessionBackupAsync    可選值,預設true,是否非同步的方式儲存到memcached。
 
sessionBackupTimeout  可選項,預設100毫秒,非同步儲存session的超時時間。

6) MSM會話共享測試
a) 訪問http://www.kevin.com,按ctrl+F5強刷頁面,發現session資訊會變,但是sessionid不會改變!說明session實現了共享! 如下,表示當前sessionid儲存到了n1這個memcached節點上了.

b) 關閉Mem-node1節點的memcached服務,繼續訪問頁面,發現sessionid儲存到了n2這個memcached節點上了,但是sessionid任然沒有改變,說明session已共享. 也就是說,關閉memcached叢集中的任意一個節點.訪問頁面,sessionid都不會改變.即可以實現memcached故障轉移!如下:

c) 關閉tomcat-node1和tomcat-node2中的任意一個節點的tomcat服務,繼續訪問頁面,發現前端從nginx負載過來的請求達到未關閉的tomcat節點上,sessionid都不會改變,任然在共享中!即可以實現tomcat故障轉移

關閉tomcat-node1節點的tomcat服務,繼續訪問頁面:

關閉tomcat-node2節點的tomcat服務,繼續訪問頁面:

特別提示:
如果memcached session manager的會話共享配置後,重啟tomcat服務沒有報錯,但是訪問頁面的時候報錯,頁面訪問失敗,如下在logs/catalina.out日誌裡發現的錯誤:SEVERE [http-nio-8080-exec-1] org.apache.coyote.http11.AbstractHttp11Processor.process Error processing request java.lang.NoSuchFieldError: attributes

這種錯誤情況基本就是jar包版本不相容導致的,需要到這裡下載跟tomcat版本相對應的jar包!!

相關文章