WEB應用開發完成後部署到Tomcat或其餘容器中供用戶訪問. 小型應用在一臺服務器上安裝Tomcat並部署WEB應用. 隨着訪問量增大, Tomcat的壓力會愈來愈大, 直至崩潰. 爲了保證WEB應用的承載能力, 須要對WEB應用進行集羣處理.html
技術發展到今天, 集羣/負載均衡已經變的相對簡單了. 下面用通俗的語言給剛入門的同窗介紹下這兩個概念:nginx
某KFC開業時只有一個點餐窗口(一臺Tocmat服務器, 能夠節約成本)對外提供點餐服務. 應對平常點餐沒有問題, 當飯口或者週末時一個窗口就會排起長隊(高併發). 不只顧客有怨言(請求響應時間長, 用戶體驗差), 服務員也會很累, 終於有一天他累倒了(Tomcat掛掉了).git
這時在側面增長了一個窗口(增長一臺Tomcat服務器)提供點餐服務, 可是不少顧客不知道新窗口, 依舊在原有窗口排起了長隊(用戶依舊訪問原來的Tomcat), 這時須要有一我的專門站在門口根據每一個窗口的排隊狀況指引顧客去哪一個窗口點餐(負載均衡器). 這我的做用是爲了讓各個窗口的點餐人數大體相等, 避免有的窗口很忙, 有的很閒. 隨着顧客增長, 點餐窗口也會相應增長(Tomcat愈來愈多).github
兩個概念是同時出現的, 沒有集羣的服務(單一Tomcat)也不存在負載均衡之說, 集羣的服務沒有負載均衡會浪費資源.web
WEB負載均衡方案不少, Nginx
+Tomcat
是經常使用的方案之一. Nginx做爲負載均衡器根據每一個Tomcat的負載狀況進行分流.正則表達式
下面咱們搭建負載均衡的WEB應用redis
準備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
: 返回5677http:// localhost:5688/port/get
: 返回5688Window下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的負載均衡.
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;
}
複製代碼
location / {
# root D:/;
# 轉發至名稱爲springsession的upstream處理
proxy_pass http://springsession;
}
複製代碼
訪問http://localhost:88/port/get
, Nginx將請求轉發至兩臺tomcat中的一個進行處理. 能夠發現請求返回的結果是不同的
5688
上, 1次由5677
處理.5688
處理.負載均衡配置好了, 有這樣一個問題:
你在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
SESSOIN ID
是不變的SESSION ID
是變化的SESSION ID
也是變化的出現上述狀況的緣由以下:
5677
, 因爲沒有SESSION, Tomcat5677
生成SESSION, ID爲1
, 並將1
返回客戶端5677
, 瀏覽器攜帶SESSION_ID=1
, Tomcat5677
找到對應的SESSION. 所以SESSION_ID爲1
5688
, 瀏覽器攜帶SESSION_ID=1
, Tomcat5688
找不到對應的SESSION, 從新生成SESSION, ID爲2
, 並將2
返回客戶端5677
, 瀏覽器攜帶SESSION_ID=2
, Tomcat5677
找不到對應的SESSION, 從新生成SESSION, ID爲3
, 並將3
返回客戶端5688
, 瀏覽器攜帶SESSION_ID=3
, Tomcat5688
找不到對應的SESSION, 從新生成SESSION, ID爲4
, 並將4
返回客戶端下面咱們來用Spring Session
來管理WEB應用的SESSION
參見文章http://www.cnblogs.com/jaign/articles/7920588.html
// Spring Session依賴
"org.springframework.session:spring-session-data-redis:2.0.5.RELEASE",
// Redis依賴
"io.lettuce:lettuce-core:5.0.4.RELEASE"
複製代碼
在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>
複製代碼
在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)
}
複製代碼
啓動項目, 屢次訪問http://localhost:88/sessionid/get
, 不管如何訪問SESSION ID
都是同樣的.
同時Redis
中也出現了當前SESSION的記錄.
使用Spring Session
後訪問集羣下的WEB應用時SESSION處理過程:
5677
, 因爲Redis
中沒有SESSION
, 所以會生成一個SESSION
並存入Redis
, ID爲1
, 並將1
返回客戶端5677
, 瀏覽器攜帶SESSION_ID=1
, Tomcat5677
在Redis
中找到了SESSION
. 所以SESSION_ID
爲1
5688
, 瀏覽器攜帶SESSION_ID=1
, Tomcat5688
在Redis
中找到了SESSION
. 所以SESSION_ID
爲1
Redis
, 再次訪問5677
, 因爲Redis
中沒有ID爲1
的SESSION
, 所以會從新生成, ID也相應變化了此時咱們已經實現了統一管理SESSION, 不管訪問任一TOMCAT均可以找到相同的SESSION.
當咱們的應用進行集羣后, 統一管理SESSION勢在必行, 實現統一管理SESSION的方式不少, 本文只是其中一種方式. 重在讓同窗們理解統一管理SESSION的重要性和他的基本原理.
https://github.com/atd681/alldemo
atd681-springsession