Tomcat實驗2之負載均衡和session

Tomcat實驗二

(1)Tomcat常見部署方式:


(2)Tomcat要實現負載均衡,必然涉及session問題:
session問題的分析:https://blog.csdn.net/wdirdo/article/details/103190013


(1)tomcat解決session問題

(1)session sticky會話黏性

a.Session綁定

  • nginx:source ip
  • HAProxy:cookie

b.優點:簡單易配置

c.缺點:如果目標服務器故障後,如果沒有做sessoin持久化,就會丟失session


(2)session複製集羣

a.Tomcat自己的提供的多播集羣,通過多播將任何一臺的session同步到其它節點

b.session複製集羣的缺點

  • Tomcat的同步節點不宜過多,互相即時通信同步session需要太多帶寬
  • 每一臺都擁有全部session,內存佔用太多

(3)session server

a.session共享服務器,使用memcached、redis做共享的Session服務器。




(2)nginx實現負載均衡

(1)nginx實現負載均衡說明

此實現先不管session問題,先實現Tomcat可以通過nginx調度


(2)實驗拓撲圖:


(3)nginx配置: 192.168.38.27

upstream tomcat {
    #ip_hash; # 先禁用看看輪詢,之後開啓開黏性
    server node1.zcpzcp.com:8080;
    server node2.zcpzcp.com:8080;
}

location / {
    root /data/nginx;
    index index.html;

    location ~* \.(jsp|do)$ {
    proxy_pass  http://tomcat;
    #proxy_pass http://node1.zcpzcp.com:8080;
    }
}


[root@centos7-27 ~]# cat /etc/hosts
192.168.38.17 node1.zcpzcp.com
192.168.38.37 node2.zcpzcp.com

(4)tomcat配置:192.168.38.17(node1.zcpzcp.com)

a.tomcat虛擬主機配置:

#<Engine name="Catalina" defaultHost="localhost">  #默認的缺省虛擬主機爲localhost
<Engine name="Catalina" defaultHost="node1.zcpzcp.com">  #將缺省主頁修改爲node1.zcpzcp.com虛擬主機

....
<Host name="node1.zcpzcp.com"  appBase="/data/webapp/"
    unpackWARs="true" autoDeploy="true">
</Host>

b.idnex.jsp文件:

[root@centos7-17 tomcat]# cat /data/webapp/ROOT/index.jsp
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>lbjsptest</title>
</head>
<body>
<div>On  <%=request.getServerName() %></div>                                #取servername
<div><%=request.getLocalAddr() + ":" + request.getLocalPort() %></div>      #取本地IP和port
<div>SessionID = <span style="color:blue"><%=session.getId() %></span></div>#顯示SessionID
<%=new Date()%>                                                             #當前時間
</body>
</html>

(5)tomcat配置:192.168.38.37(node2.zcpzcp.com)

a.tomcat虛擬主機配置:

#<Engine name="Catalina" defaultHost="localhost">  #默認的缺省虛擬主機爲localhost
<Engine name="Catalina" defaultHost="node2.zcpzcp.com">  #將缺省主頁修改爲node1.zcpzcp.com虛擬主機

....
<Host name="node2.zcpzcp.com"  appBase="/data/webapp/"
    unpackWARs="true" autoDeploy="true">
</Host>

b.idnex.jsp文件:

[root@centos7-37 tomcat]# cat /data/webapp/ROOT/index.jsp
<%@ page import="java.util.*" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>lbjsptest</title>
</head>
<body>
<div>On  <%=request.getServerName() %></div>
<div><%=request.getLocalAddr() + ":" + request.getLocalPort() %></div>
<div>SessionID = <span style="color:blue"><%=session.getId() %></span></div>
<%=new Date()%>
</body>
</html>

(6)Windows的解析文件配置:C:\Windows\System32\drivers\etc\hosts

192.168.38.17 node1.zcpzcp.com
192.168.38.37 node2.zcpzcp.com
192.168.38.27 www.zcpzcp.com

(7)測試:http://www.zcpzcp.com/index.jsp

a.瀏覽器第一次訪問:

b.ctrl+r 刷新訪問:

c.ctrl+r 刷新訪問:

總結:由於負載均衡,瀏覽器每次被調度至不同的服務器,但是sessionID瀏覽器只能保存一份(相同域名),因此sessionID一直改變,無法實現瀏覽器與web server的會話保持。




(3)session綁定的方式解決session問題

(1)基於session綁定:nginx上實現基於源地址hash

upstream tomcat {
    ip_hash; # 先禁用看看輪詢,之後開啓開黏性
    server node1.zcpzcp.com:8080;
    server node2.zcpzcp.com:8080;
}

==>預計看到的現象:當瀏覽器訪問:http://www.zcpzcp.com/index.jsp,將一直被調度至web server中其他一臺Tomcat上。


(2)測試:基於源地址hash

a.ctrl+r 刷新訪問:

a.ctrl+r 刷新訪問:

==>現象:僅有時間改變,sessionID和web server的tomcat均未發生改變


(3)總結:基於源地址hash

①使用基於源地址解決session問題,是一種不好的處理方式,因爲web server做負載均衡的目的就是實現將訪問的併發分攤至後端各個服務器,但是基於源地址hash,調度始終指向後端某臺服務器。

②現在局域網上網基本都是通過SNAT實現的,因此整個局域網上網映射的公網IP相同,基於源地址hash則認爲整個局域網的人都屬於同一用戶。

③其實session綁定中基於cookie做hash僅僅是將用戶根據瀏覽器的不同調度至後端服務器,即將同一局域網和同一主機的不同瀏覽器分辨出爲不同用戶,依舊解決不了後端服務器故障的問題。

④session綁定沒能從根本上解決問題,即瀏覽器中的sessionID必須去有sessionID這臺後端服務器纔能有用,如果帶着此sessionID被調度至別的後端服務器,後端服務器找不到此sessionID時,將重發sessionID,等帶着此sessionID去訪問後端服務器別的主機時,別的主機也找不到此sessionID,本質上解決問題:需要所有的後端服務器均能識別瀏覽器本次訪問時所攜帶的sessionID。

注1:web server的tomcat代表後端動態服務器,即後端動態服務器使用tomcat舉例。
注2:session綁定的方式實現雖然簡單,但是實際上卻不會使用session綁定的方式處理會話狀態保持的問題。




(4)後端服務器session同步解決session問題

(1)tomcat session集羣

tomcat session集羣即後端服務器如果有多個tomcat時,多個tomcat之間將對session進行數據同步,則每一個tomcat節點都有整個tomcat整個集羣的session信息。因此瀏覽器無論被調度至哪一個tomcat節點,tomcat均能識別sessionID,進而完成會話狀態保持。


(2)實現tomcat集羣的配置

官方文檔: https://tomcat.apache.org/tomcat-8.5-doc/cluster-howto.html

  • 建議直接去官網粘貼源碼,只需將NioReceiver中的address地址修改爲當前主機用於tomcat集羣通信的IP
<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="230.100.100.8"     #tomcat配置相同的多播地址和端口就是同組的tomcat,可以相互通信,心跳檢測之類的
		port="45564"
		frequency="500"
		dropTime="3000"/>
	<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
		address="auto"          #如果此處的address=auto將選擇則127.0.0.1,應該填寫當前主機用於tomcat集羣通信的IP
		port="4000"             #同組tomcat集羣之間發送session信息的端口
		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.MessageDispatchInterceptor"/>
  </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"/>
  <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
  • 這段代碼可以放置的位置:
    ①engine(對所以虛擬主機生效)即所有虛擬主機都可以啓用Session複製
    ②host(對當前虛擬主機生效)即該虛擬主機可以啓用Session複製

(3)修改配置文件:192.168.38.17

① <Engine name="Catalina" defaultHost="node1.zcpzcp.com" jvmRoute="Tomcat17">
在默認的添加了jvmRoute="Tomcat17"  ==>爲了後續實驗觀察現象


②在host虛擬主機可以啓用Session複製
<Host name="node1.zcpzcp.com"  appBase="/data/webapp/"
          unpackWARs="true" autoDeploy="true">
...
==>添加tomcat集羣的配置
...
</Host>

(4)修改配置文件:192.168.38.37

① <Engine name="Catalina" defaultHost="node2.zcpzcp.com" jvmRoute="Tomcat37">
在默認的添加了jvmRoute="Tomcat37"  ==>爲了後續實驗觀察現象


②在host虛擬主機可以啓用Session複製
<Host name="node2.zcpzcp.com"  appBase="/data/webapp/"
          unpackWARs="true" autoDeploy="true">
...
==>添加tomcat集羣的配置
...
</Host>

(5)Make sure your web.xml has the <distributable/> element

注意:僅在虛擬主機開啓session複製是不夠的!如下是tomcat官方提供的實現:

注意:集羣實驗中各集羣節點一定要保證時間一致,時間不一致,集羣的實現肯定有問題。

[root@centos7-37 tomcat]#mkdir /data/webapp/ROOT/WEB-INF
[root@centos7-37 tomcat]#cp conf/web.xml /data/webapp/ROOT/WEB-INF/
[root@centos7-37 tomcat]#vim /data/webapp/ROOT/WEB-INF/web.xml 
    </welcome-file-list>

    <distributable/>  #在/data/webapp/ROOT/WEB-INF/web.xml最後添加<distributable/>

    </web-app>

192.168.38.17(tomcat另一節點)做以上相同操作,也需要/data/webapp/ROOT/WEB-INF/web.xml文件並添加<distributable/>


(6)session同步測試:

①打開瀏覽器訪問此域名:http://www.zcpzcp.com/index.jsp

②ctrl+r刷新:

==>sessionID未改變,無論調度至192.168.38.17還是192.168.38.37


(7)總結:tomcat session集羣的方式解決session問題

①每個tomcat節點都需保存整個節點的session信息,tomcat集羣中主機數目小,session信息少還可以使用此方法實現,若session信息較多,那麼對於tomcat內存的要求。

②隨着tomcat節點數目的增加,內部通信的代價也將逐漸增加。

③session同步解決session問題兩個問題:後端動態服務器的內存和內部通信,適用於小場景,session較少,動態服務器集羣數量較少。




(5)session共享

(1)msm

msm(memcached session manager)提供將Tomcat的session保持到memcached或redis的程序,可以實現高可用。

目前項目託管在Github, https://github.com/magro/memcached-session-manager

支持Tomcat的6.x、7.x、8.x、9.x。

(2)tomcat與緩存服務:

官網說明: https://github.com/magro/memcached-session-manager/wiki/SetupAndConfiguration

  • Tomcat的Session管理類,Tomcat版本不同
    memcached-session-manager-2.3.2.jar
    memcached-session-manager-tc8-2.3.2.jar
  • Session數據的序列化、反序列化類
    官方推薦kyro
    在webapp中WEB-INF/lib/下
  • 驅動類
    memcached(spymemcached.jar)
    Redis(jedis.jar)

(3)tomcat需要安裝的包:

tomcat將session發送給緩存服務:

支持序列化的jar包:

將spymemcached.jar、memcached-session-manage、kyro相關的jar文件都放到Tomcat的lib目錄中去,這個目錄是 $CATALINA_HOME/lib/ ,對應本次安裝就是/usr/local/tomcat/lib




sticky模式

(1)sticky原理:

①官網畫的拓撲圖

②按照官網畫的圖重新繪製的拓撲圖

③原理闡述:
   當請求結束時Tomcat的session會送給memcached備份。即Tomcat session爲主session,memcached session爲備session,使用memcached相當於備份了一份Session。查詢Session時Tomcat會優先使用自己內存的Session,Tomcat通過jvmRoute發現不是自己的Session,便從memcached中找到該Session,更新本機Session,請求完成後更新memcached。

④示例:

  • tomcat1中有個session1 那麼session1保存的位置:tomcat1 和 memcache2
    如果memcache2 節點故障,那麼session1 的信息纔會被迫存放至 memcache1。

⑤總結:

  • sticky模式最關鍵的是:使用tomcat自己的內存作爲存儲session的主,然後交叉以對方的memcache緩存作爲’備份的主’(備份的主簡單爲數據優先備份存放的位置)。

sticky的實現

注:此處tomcat操作相同,省略一個節點的tomcat操作。

①安裝memcache

[root@centos7-17 tomcat]# yum install -y memcached
[root@centos7-17 tomcat]# systemctl start memcached.service

②tomcat配置

  • node1配置中爲failoverNodes=“n17”, node2配置爲failoverNodes=“n37”
  • failoverNodes故障轉移節點,n17是備用節點,n37是主存儲節點。另一臺Tomcat將n17改爲n37,其主節點是n17,備用節點是n37
  • memcached的節點們;n17、n37只是別名,可以重新命名。
conf/context.xml 中

<Context>
    ...
    <Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
        memcachedNodes="n17:192.168.38.17:11211,n37:192.168.38.37:11211"
        failoverNodes="n17"      #故障轉移節點
        requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"

    transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
    />
</Context>  

③配置tomcat配置文件截圖:

④配置成功的顯示日誌:


sticky的測試:

①瀏覽器訪問:http://www.zcpzcp.com/index.jsp

==>調度至192.168.38.17主機,那麼session信息應該保存在192.168.38.37主機

②查看memcache的數據:

memcache的數據:

[('192.168.38.17:11211 (1)', {'1DF770481060C28E523C7E0312C3F09C-n17.Tomcat37': '[99 b; 1574424758 s]'})

('192.168.38.37:11211 (1)', {'1DF770481060C28E523C7E0312C3F09C-n37.Tomcat17': '[99 b; 1574424311 s]'})]


==>sessionID無論在17或者37主機均爲:1DF770481060C28E523C7E0312C3F09C



non-sticky模式

(1)non-sticky模式原理

①拓撲圖:

②原理:

  • 從msm 1.4.0之後開始支持non-sticky模式。

  • Tomcat session爲中轉Session,如果n1爲主session,n2爲備session。產生的新的Session會發送給主、備memcached,並清除本地Session。

  • 其中memcache主節點下線,則備用節點轉正,主節點上線,將成爲目前的備用節點。


(2)non-sticky的tomcat配置:

<Context>
...
<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
    memcachedNodes="n17:192.168.142.152:11211,n37:192.168.142.153:11211"
    sticky="false"
    sessionBackupAsync="false"
    lockingMode="uriPattern:/path1|/path2"
    requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"

    transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
/>
</Context>


主要變化是這三行:
    sticky="false"
    sessionBackupAsync="false"
    lockingMode="uriPattern:/path1|/path2"
  • 修改配置後重啓memcache 和 tomcat服務,重啓memcache主要是將上次實現的數據清空

(3)non-sticky測試:

①瀏覽器重新訪問:http://www.zcpzcp.com/index.jsp

②查看memcache中保存的session信息

③當17主機的memcache服務停止,瀏覽器繼續訪問和memcache的信息保存:


==>sessionID仍然沒有改變,但是memcache存儲的session只能放置37主機。




最後附上:查看memcache內存信息的python程序:

  • 注:pip install python-memcached
  • 運行時需要安裝python-memcached,作爲memcache客戶端連接memcache
import memcache # pip install python-memcached

mc = memcache.Client(['192.168.142.152:11211','192.168.142.153:11211'], debug=True)

stats = mc.get_stats()[0]
print(stats)
for k,v in stats[1].items():
    print(k, v)

print('-' * 30)
# 查看全部key
print(mc.get_stats('items')) # stats items 返回 items:5:number 1
print('-' * 30)
print(mc.get_stats('cachedump 5 0')) # stats cachedump 5 0 # 5和上面的items返回的值有關;0表示全部



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章