1. 程式人生 > >搭建Tomcat叢集&通過Redis快取共享session的一種流行方案

搭建Tomcat叢集&通過Redis快取共享session的一種流行方案

為什麼要共享session?

我們使用單臺Tomcat的時候不會有共享sesssion的疑慮,只要使用Tomcat的預設配置即可,session即可儲存在Tomcat上。

但是隨著業務的擴大,增加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存取區一樣了。

怎麼做呢?

有了上述基本的概念,我們就要開始真正施行了。

1. 持久化Tomcat Session到Redis中

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叢集該如何部署。

使用這個專案非常簡單,如果在Tomcat6或Tomcat7下部署,直接使用專案release出的jar檔案到Tomcat的lib下即可,準確來說還需要引入它的其他幾個依賴(括號中為建議的):

tomcat-redis-session-manager-VERSION.jar(v1.2)
commons-pool2-VERSION.jar(v2.2)
jedis-VERSION.jar(v2.5.2)

引入後需要在tomcat下修改conf/context.xml

<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來處理。

需要注意的是,如果在Tomcat8下按照上述部署,會在啟動時報錯,筆者檢視過原因,是tomcat-redis-session-manager最後更新的年代相隔較久,程式碼中使用的Tomcat api出現了過時刪去的情況,在Tomcat8下會出現問題,如果想在Tomcat8下使用,需要自行對過時的api進行修改,替換成新的Tomcat api。筆者自己修改了未經嚴格驗證的一個版本,可供使用Tomcat8的讀者試用:
https://github.com/jinhaoplus/tomcat-redis-session-manager

2. nginx反向代理的負載均衡

雖然這不是本文的重點,但是使用負載均衡在搭建叢集的過程中重要性不言而喻,使用nginx預設的輪詢機制,我們可以將前端的瀏覽器請求轉發到不同的Tomcat例項上。
首先來講講正向代理和反向代理,一言以蔽之:正向代理幫助內網client訪問外網server用,反向代理將來自外網client的請求f轉發到到內網server。
最實際的區別是使用二者時正向代理需要使用者主動配置,而反向代理對使用者透明,不需要使用者做主動配置。
「代理」是指代人理事,即代理伺服器是為其他人或機器服務的。
正向代理是替內網中的使用者訪問外網服務的(即代替使用者去訪問外網),使用者和外網之間的溝通全部交由正向代理伺服器完成,使用者的請求不發給外網伺服器而發給代理伺服器讓其代為處理,這個過程是隱藏使用者的。
反向代理是為真正的服務節點機器服務的(即代替真正的服務節點機器去提供服務),代理伺服器接收來自外界的請求,並將請求轉給真正的服務節點機器,使用者不與真正的機器打交道
,這個過程是隱藏真正的服務例項機器的。

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例項上,以此實現負載均衡。

3. 基於sentinel的redis叢集搭建

上文介紹的方法其實已經可以搭建一個完整的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。

sentinel2sentinel3配置相同。

這樣之後啟動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"/>

這個叢集搭建完成後,我們的系統將會更為穩定: