咱們使用單臺Tomcat的時候不會有共享sesssion的疑慮,只要使用Tomcat的默認配置便可,session便可存儲在Tomcat上。html
可是隨着業務的擴大,增長Tomcat節點構成Tomcat集羣大勢所趨,分佈式帶來了增長更大規模併發請求的優點,可是也隨之到來了一個問題,每一個Tomcat只存儲來訪問本身的請求產生的session,若是Tomcat-A已經爲客戶端C建立了會話session,那麼Tomcat-B並不知道客戶端已與集羣中的Tomcat-A產生了會話,在訪問時就會爲C再建立一份session,若是是基於session的驗證會話權限的接口(如用戶登陸認證後纔可訪問的數據接口),將會致使在訪問集羣中不一樣節點的時候重複認證。session的不共享致使原來的會話管理機制在Tomcat集羣中沒法工做。前端
因此,若是有一個Tomcat集羣都能訪問的公共session存取區就行了,基於這個概念,咱們想到了使用Redis來作這個session公共存取區,這樣子的話就有一個統一管理回話的地方了。回看咱們上文提到的例子,若是Tomcat-A已經爲客戶端C建立了會話session,這個session信息會直接存儲在公共的Redis裏面,那麼Tomcat-B就能夠到公共session存儲區裏得到已爲C產生的session,這樣的結果是集羣的公共session存取區在邏輯上就像一個tomcat的內部session存取區同樣了。nginx
有了上述基本的概念,咱們就要開始真正施行了。git
Tomcat提供了一個開放的session管理和持久化的org.apache.catalina.session.ManagerBase
,繼承這個抽象類並作一些簡單的配置,便可讓你的session管理類接管Tomcat的session讀取和持久化。固然,咱們在這裏使用了一個流行的開源項目:
https://github.com/jcoleman/tomcat-redis-session-manager
,它已經爲咱們準備好了這樣的一個管理類,只要將這個管理類配置在Tomcat中便可發揮功能。它能夠幫助咱們將tomcat的session存入咱們指定的redis,甚至支持redis在sentinel模式調度的redis集羣,稍後咱們也將詳述這樣的redis集羣該如何部署。github
使用這個項目很是簡單,若是在Tomcat6或Tomcat7下部署,直接使用項目release出的jar文件到Tomcat的lib
下便可,準確來講還須要引入它的其餘幾個依賴(括號中爲建議的):redis
tomcat-redis-session-manager-VERSION.jar(v1.2) commons-pool2-VERSION.jar(v2.2) jedis-VERSION.jar(v2.5.2)
引入後須要在tomcat下修改conf/context.xml
:apache
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" /> <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager" host="{redis.host}" port="{redis.port}" database="{redis.dbnum}" maxInactiveInterval="60"/>
這樣一來咱們的tomcat便可把session的管理交由咱們配置的redis來處理。api
須要注意的是,若是在Tomcat8下按照上述部署,會在啓動時報錯,筆者查看過緣由,是tomcat-redis-session-manager
最後更新的年代相隔較久,代碼中使用的Tomcat api出現了過期刪去的狀況,在Tomcat8下會出現問題,若是想在Tomcat8下使用,須要自行對過期的api進行修改,替換成新的Tomcat api。筆者本身修改了未經嚴格驗證的一個版本,可供使用Tomcat8的讀者試用:
https://github.com/jinhaoplus/tomcat-redis-session-manager瀏覽器
雖然這不是本文的重點,可是使用負載均衡在搭建集羣的過程當中重要性不言而喻,使用nginx默認的輪詢機制,咱們能夠將前端的瀏覽器請求轉發到不一樣的Tomcat實例上。
首先來說講正向代理和反向代理,一言以蔽之:正向代理幫助內網client訪問外網server用,反向代理未來自外網client的請求f轉發到到內網server。
最實際的區別是使用兩者時正向代理須要用戶主動配置,而反向代理對用戶透明,不須要用戶作主動配置。
「代理」是指代人理事,即代理服務器是爲其餘人或機器服務的。
正向代理是替內網中的用戶訪問外網服務的(即代替用戶去訪問外網),用戶和外網之間的溝通所有交由正向代理服務器完成,用戶的請求不發給外網服務器而發給代理服務器讓其代爲處理,這個過程是隱藏用戶的。
反向代理是爲真正的服務節點機器服務的(即代替真正的服務節點機器去提供服務),代理服務器接收來自外界的請求,並將請求轉給真正的服務節點機器,用戶不與真正的機器打交道
,這個過程是隱藏真正的服務實例機器的。tomcat
nginx能夠做爲高效的反向代理服務器,同時起到了負載均衡的做用。若是想要使用反向代理Tomcat集羣的負載,方法也很是簡單,只須要在其配置nginx.conf
中將負載的Tomcat集羣的實際地址加入upstream,並將locate導向配好的upstream便可:
http{ ... upstream tomcats { server <tomcat1_ip>:<tomcat1_port>; server <tomcat2_ip>:<tomcat2_port>; ... server <tomcatn_ip>:<tomcatn_port>; } ... server { listen 80; ... location / { root html; index index.html index.htm; proxy_pass http://tomcats; } } }
默認的輪詢機制將每次請求都發至不一樣的Tomcat實例上,以此實現負載均衡。
上文介紹的方法其實已經能夠搭建一個完整的Tomcat集羣了,若是系統想要一個更安全可靠的環境,那麼nginx其實也能夠作集羣,這裏略去不說,咱們想要說的是redis集羣。
上面咱們已經說到redis是session的公共存儲區,若是redis不幸掛掉的話將會致使致命的問題,由於無session源可取,Tomcat中有session讀取的接口會直接報錯。因此搭建一個redis集羣仍是頗有必要的,幸虧redis對分佈式HA的搭建支持得很好,原生即有一套sentinel哨兵機制便可用。
以sentinel模式啓動的redis實例起到了監控者的做用,redis集羣以master-slave的模式啓動,消息再也不直接發給redis實例,而是發給sentinel,由sentinel同步至全部的redis實例,若是出現redismaster實例掛掉的狀況,會由sentinel發現,根據配置還能夠由sentinel本身組成的集羣去選舉產生新的master,新的master將會承擔起做用,起到了災難自動回恢復的做用。
這裏來講一下sentinel集羣的配置:
咱們使用兩個redis實例來組成master-slave,須要三個sentinel組成哨兵集羣來監控兩個redis實例,在master出現問題的時候選舉產生新的master。
路徑假設以下:redis1
redis2
sentinel1
sentinel2
sentinel3
配置redis1/redis.conf
爲master:
bind 127.0.0.1 port 6379
配置redis2/redis.conf
爲redis1的slave:
bind 127.0.0.1 port 6379 slaveof <redis1_ip> 6379
配置sentinel1/redis-sentinel.conf
:
port 26379 sentinel monitor mymaster <redis1_ip> 6379 2
指定redis1爲master,slave信息會在啓動後被sentinel監聽到並自動寫入到配置文件中,所以不須要手動寫入,最後的quorum表示當有2個sentinel判斷master掛掉的時候便可選舉slave爲新的master。
sentinel2
,sentinel3
配置相同。
這樣以後啓動redis和sentinel集羣,便可構建一個高可用的redis集羣。能夠嘗試一下把redis1
這個master掛掉,sentinel就會探查到而且在2個sentinel都探查到的時候即會選舉產生新的master:
# +monitor master mymaster <redis1-ip> 6379 quorum 2 # +sdown master mymaster <redis1-ip> 6379
同時咱們的Tomcat配置也將改成使用sentinel集羣的版本:
<Valve className="com.orangefunction.tomcat.redissessions.RedisSessionHandlerValve" /> <Manager className="com.orangefunction.tomcat.redissessions.RedisSessionManager" sentinelMaster="mymaster" sentinels="<sentinel1-ip>:26379,<sentinel2-ip>:26379,<sentinel3-ip>:26379" maxInactiveInterval="60"/>
這個集羣搭建完成後,咱們的系統將會更爲穩定: