SpringSession+Redis實現集羣會話共享

0) 前言

WEB應用開發完成後部署到Tomcat或其餘容器中供用戶訪問. 小型應用在一臺服務器上安裝Tomcat並部署WEB應用. 隨着訪問量增大, Tomcat的壓力會愈來愈大, 直至崩潰. 爲了保證WEB應用的承載能力, 須要對WEB應用進行集羣處理.html

技術發展到今天, 集羣/負載均衡已經變的相對簡單了. 下面用通俗的語言給剛入門的同窗介紹下這兩個概念:nginx

某KFC開業時只有一個點餐窗口(一臺Tocmat服務器, 能夠節約成本)對外提供點餐服務. 應對平常點餐沒有問題, 當飯口或者週末時一個窗口就會排起長隊(高併發). 不只顧客有怨言(請求響應時間長, 用戶體驗差), 服務員也會很累, 終於有一天他累倒了(Tomcat掛掉了).git

這時在側面增長了一個窗口(增長一臺Tomcat服務器)提供點餐服務, 可是不少顧客不知道新窗口, 依舊在原有窗口排起了長隊(用戶依舊訪問原來的Tomcat), 這時須要有一我的專門站在門口根據每一個窗口的排隊狀況指引顧客去哪一個窗口點餐(負載均衡器). 這我的做用是爲了讓各個窗口的點餐人數大體相等, 避免有的窗口很忙, 有的很閒. 隨着顧客增長, 點餐窗口也會相應增長(Tomcat愈來愈多).github

  • 集羣: 一羣服務器集合在一塊兒提供服務, 上例中多個點餐窗口(多臺Tomcat)共同提供點餐服務就是集羣.
  • 負載均衡: 讓集羣中每一個點餐窗口(每一個Tomcat)的負載狀況保持均衡, 不要出現某一個或幾個太空閒.

兩個概念是同時出現的, 沒有集羣的服務(單一Tomcat)也不存在負載均衡之說, 集羣的服務沒有負載均衡會浪費資源.web

WEB負載均衡方案不少, Nginx+Tomcat是經常使用的方案之一. Nginx做爲負載均衡器根據每一個Tomcat的負載狀況進行分流.正則表達式

  • 每一個Tomcat都至關於點餐窗口, 均可以提供點餐服務
  • 每次要點餐都得先通過Nginx
  • Nginx會根據每一個窗口的空閒狀況進行分配用戶去哪一個窗口點餐
  • 第一次在1號窗口點餐, 點完後立刻再次點餐, 有可能被分配到2號窗口

下面咱們搭建負載均衡的WEB應用redis

1) 搭建WEB應用

準備WEB應用, 用兩個Tomcat部署, 測試時爲了可以區分請求是由哪一個Tomcat進行處理, 將Tomcat端口號做爲結果返回.spring

/**
 * 獲取部署項目的Tomcat端口號
 */
@RequestMapping("/port/get")
@ResponseBody
public String getPort(HttpServletRequest request) {
    return String.valueOf(request.getLocalPort());
}
複製代碼

本例中分別使用5677, 5688兩個端口部署該項目. 訪問/port/get請求返回結果爲Tomcat的端口號數據庫

  • http:// localhost:5677/port/get : 返回5677
  • http:// localhost:5688/port/get : 返回5688

2) Nginx配置負載均衡

Window下Nginx安裝比較簡單, 不會安裝的同窗自行百度, 簡單介紹下Nginx配置文件: nginx.conf瀏覽器

# Nginx進程數
worker_processes  1;
 
events {
    # 最大併發連接數
    worker_connections  1024;
}
 
# Nginx處理HTTP請求相關的配置
# http不能重複, 全局惟一
http {
 
    # 虛擬主機, 可配置多個虛擬主機
    # Nginx監聽88,89,90三個端口, 可配置三個server
    server {
        # 端口號, 訪問88端口會都按照該server下的配置進行處理
        listen       88;
        # 主機名稱
        server_name  localhost;
        # 根據正則表達式匹配URL, 匹配到的URL按照該location下的配置進行處理
        # /表明訪問88端口的全部請求
        location / {
            # 靜態資源所在根目錄, 會從該目錄下查找靜態資源
            # 例: 訪問/a.html, 找到D:/a.html並返回
            root  D:/;
        }
    }
 
}
複製代碼

上述配置文件最基礎的Nginx配置, 當咱們訪問http://localhost:88時會由Nginx處理, 下面咱們配置Nginx的負載均衡.

  • 配置1)中定義的兩個tomcat, 在http節點下添加以下代碼:
# 定義須要進行負載均衡的服務器信息
# upstream爲關鍵字, springsession爲自定義的名稱
# server爲關鍵字, 表明一個服務或服務(一個tomcat)
# server的內容爲服務器的信息, 形式爲ip:端口
# weight定義了服務器負載的權重, 每4次請求有3次轉發到5688, 1次到5677
upstream springsession { 
    server localhost:5677 weight=1; 
    server localhost:5688 weight=3; 
}
複製代碼
  • 配置當訪問Nginx的全部請求轉發至兩個服務器處理
location / {
    # root D:/;
    # 轉發至名稱爲springsession的upstream處理
    proxy_pass http://springsession; 
}
複製代碼

3) 測試負載均衡

訪問http://localhost:88/port/get, Nginx將請求轉發至兩臺tomcat中的一個進行處理. 能夠發現請求返回的結果是不同的

  • 根據配置的權重, 每4次訪問有3次由5688上, 1次由5677處理.
  • 權重配置只是最終平均值爲3/4和1/4, 不必定是前三次訪問都會由5688處理.
  • 不配置weight時, 一次請求兩個tomcat被分配到的機率各佔50%

負載均衡配置好了, 有這樣一個問題:

你在1號窗口點餐時把鑰匙暫存到該窗口, 下次在點餐可能被分配到2號窗口或其餘窗口(也有可能分配到1號窗口), 那麼在其餘窗口取鑰匙顯然是行不通的. 由於其餘窗口沒有你的鑰匙. 這時你只能祈禱能快速把你分配到1號窗口.

若是保存鑰匙的操做變爲在SESSION中保存信息, 那麼當你的請求被Tomcat1處理時, Tomcat1會爲你生成一個SESSION, 你在SESSION裏面設置了信息, 下次你的請求被分配到Tomcat2處理, Tomcat2又會爲你生成一個SESSION. 這是兩個獨立的而且不共享的SESSION. 所以你是不可能的在Tomcat2中獲取你在Tomcat1中保存的信息.

登陸的原理其實就是在SESSION中保存登陸狀態, 按照上面的分析, 登陸在集羣部署的服務中就失效了. 在Tomcat1中登陸, 下次訪問Tomcat2, 此時經過SESSION判斷登陸狀態獲得的必定是未登陸, 還須要再次登陸. 用戶瘋, 你瘋, 老闆也會瘋...


若是有一個公用位置用來存放東西, 全部的點餐窗口都在公用位置存取顧客物品, 上面的問題就迎刃而解了.

這就是本文重點: 統一管理集羣下各WEB應用的SESSION.

  • 容器的選擇: 咱們須要一個可以統一存放SESSION的容器. 從如下3點分析, Redis無疑是最合適的. SESSION是常常被讀取的, 所以數據庫, 文件系統均不適合, 最好是從內存操做. SESSION是有ID的, 一個ID對應一個SESSION, 最好是一個K/V的容器 SESSION是有時效性的(時間長不用, 須要刪除). 最好可以設置過時時間

  • SESSION存取機制: 因爲SESSION是Tomcat生成的, 所以首先想到的是修改Tomcat的SESSION機制, 從Redis中存取SESSION, 這樣會帶來一個問題, 假設Tocmat升級了, 咱們還須要從新對Tomcat進行修改. 所以這個方案可行性較差. 咱們能夠這樣考慮, 即便Tomcat生成了SESSION, 咱們也是在WEB應用中使用, 爲何不在WEB應用中從新生成一個SESSION呢, 編寫這樣一個過濾器, 在進入WEB應用以前, 拋棄Tomcat的SESSION, 從Redis中獲取SESSION.


恰巧有這樣一個框架幫助咱們完成上面的想法, 只須要配置一下便可實現統一管理SESSION. 他就是Spring Session.

爲了對Spring Session的功能印象深入, 咱們先來測試一下沒有Spring Session時咱們的集羣應用是怎樣處理SESSION的

把咱們負載均衡的WEB應用中增長一個控制器方法, 把每次的SESSION ID輸出一下.

/**
  * 獲取部署項目的SESSION ID
  */
@RequestMapping("/sessionid/get")
@ResponseBody
public String getPort(HttpServletRequest request, HttpSession session) {
    int port = request.getLocalPort(); // 端口
    String sessionId = request.getSession().getId(); // SESSION ID
 
    return "port: " + port + ", session id: " + sessionId;
}
複製代碼

啓動項目, 屢次訪問http://localhost:88/sessionid/get

  • 兩次訪問都在同一Tomcat下的SESSOIN ID是不變的
  • 兩次訪問在不一樣的Tomcat下的SESSION ID是變化的
  • 訪問不一樣的Tomcat後, 再次訪問同一Tomcat時SESSION ID也是變化的

出現上述狀況的緣由以下:

  1. 訪問5677, 因爲沒有SESSION, Tomcat5677生成SESSION, ID爲1, 並將1返回客戶端
  2. 訪問5677, 瀏覽器攜帶SESSION_ID=1, Tomcat5677找到對應的SESSION. 所以SESSION_ID爲1
  3. 訪問5688, 瀏覽器攜帶SESSION_ID=1, Tomcat5688找不到對應的SESSION, 從新生成SESSION, ID爲2, 並將2返回客戶端
  4. 訪問5677, 瀏覽器攜帶SESSION_ID=2, Tomcat5677找不到對應的SESSION, 從新生成SESSION, ID爲3, 並將3返回客戶端
  5. 訪問5688, 瀏覽器攜帶SESSION_ID=3, Tomcat5688找不到對應的SESSION, 從新生成SESSION, ID爲4, 並將4返回客戶端

4) 統一SESSION管理

下面咱們來用Spring Session來管理WEB應用的SESSION

1) 安裝Redis並開啓

參見文章http://www.cnblogs.com/jaign/articles/7920588.html

2) 添加Spring Session依賴

// Spring Session依賴
"org.springframework.session:spring-session-data-redis:2.0.5.RELEASE",
// Redis依賴
"io.lettuce:lettuce-core:5.0.4.RELEASE"
複製代碼

3) 配置Spring Session過濾器

Web.xml中配置Spring Session提供的過濾器, 該過濾器主要負責將Tomcat生成的SESSION替換成Redis中保存的SESSION.

<!-- Spring Session過濾器 -->
<!-- 負責在進入WEB應用以前將Tomcat生成的SESSION替換爲Redis中的SESSION -->
<filter>
	<filter-name>springSessionRepositoryFilter</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>springSessionRepositoryFilter</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
複製代碼

4) SpringSession/Redis配置

在Spring配置文件中增長Spring Session配置和Redis配置

beans {
 
    xmlns context: "http://www.springframework.org/schema/context"
 
    // 啓動註解方式
    context.'annotation-config'()
 
    // 配置Spring Session
    // 其實是配置Web.xml中使用的Spring Session過濾器
    // 將Tomcat的Session替換爲Redis中管理的Session
    sessionConfig(RedisHttpSessionConfiguration)
 
    // 配置Redis客戶端鏈接
    // 默認鏈接本地6379端口
    lettuce(LettuceConnectionFactory)
 
}
複製代碼

5) 測試

啓動項目, 屢次訪問http://localhost:88/sessionid/get, 不管如何訪問SESSION ID都是同樣的.

同時Redis中也出現了當前SESSION的記錄.

使用Spring Session後訪問集羣下的WEB應用時SESSION處理過程:

  1. 訪問5677, 因爲Redis中沒有SESSION, 所以會生成一個SESSION並存入Redis, ID爲1, 並將1返回客戶端
  2. 訪問5677, 瀏覽器攜帶SESSION_ID=1, Tomcat5677Redis中找到了SESSION. 所以SESSION_ID1
  3. 訪問5688, 瀏覽器攜帶SESSION_ID=1, Tomcat5688Redis中找到了SESSION. 所以SESSION_ID1
  4. 清除Redis, 再次訪問5677, 因爲Redis中沒有ID爲1SESSION, 所以會從新生成, ID也相應變化了

5) 示例代碼

此時咱們已經實現了統一管理SESSION, 不管訪問任一TOMCAT均可以找到相同的SESSION.

當咱們的應用進行集羣后, 統一管理SESSION勢在必行, 實現統一管理SESSION的方式不少, 本文只是其中一種方式. 重在讓同窗們理解統一管理SESSION的重要性和他的基本原理.

  • 示例代碼地址: https://github.com/atd681/alldemo
  • 示例項目名稱: atd681-springsession
相關文章
相關標籤/搜索