公司有一個 Web 管理系統,使用 Tomcat 進行部署。因爲是後臺管理系統,全部的網頁都須要登陸受權以後才能進行相應的操做。前端
起初這個系統的用的人也很少,爲了節省資源,這個系統僅僅只是單機部署。後來隨着用的人愈來愈多,單機已經有點扛不住了,因而我決定再部署了一臺機器。spring
這時後端系統有兩臺服務,因而咱們使用 Nginx 做爲反向代理,總體架構圖以下:後端
這個架構圖想必你們應該比較熟悉,如今主流的 Web 系統應該都是這麼部署。瀏覽器
通過一些調試以後,在一個夜深人靜的晚上,將這套系統部署到了生產。本覺得沒有什麼事的,很穩的交給測試小姐姐開始測試。緩存
這一測,出了大問題!測試小姐姐反饋,登陸事後,沒過一會又須要登陸,操做好幾回都是這樣。網絡
檢查了一下,系統應用,配置什麼也沒問題,那到底哪裏出了問題?session
這個時候組長剛準備下班,看到咱們這裏有問題,因而過來了看了一下。簡單瞭解的一下基本狀況,很快就找到了問題的緣由,而後在 Nginx 端修改了下配置,重啓解決了問題。架構
先點後贊,養成習慣~關注公號『程序通事』,快來呀!!
解決完問題,組長坐下解釋了問題緣由:分佈式一致性 Session。負載均衡
原先咱們登陸以後將會把用戶登陸信息放在 Session 中,用戶每次操做首先先校驗 Session 是否存在用戶信息,若是不存在將會強制讓用戶先去登陸。分佈式
原先架構的中咱們只有一臺應用系統,全部操做都在一臺 Tomcat 上,這固然沒有什麼問題。
可是如今咱們部署了兩臺系統,因爲 Nginx 使用默認負載均衡策略(輪詢),請求將會按照時間順序逐一分發到後端應用上。
也就是說剛開始咱們在 Tomcat1 登陸以後,用戶信息放在 Tomcat1 的 Session 裏。過了一會,請求又被 Nginx 分發到了 Tomcat2 上,這時 Tomcat2 上 Session 裏尚未用戶信息,因而又要登陸。
另外因爲咱們系統採用單點登陸的方式,Tomcat2 登陸以後會將 Tomcat1 登陸信息失效,因而乎等到 Nginx 再把流量分發到 Tomcat1 時,Session 中用戶登陸信息已經失效,又要從新登陸。
知道了問題,固然想知道解決辦法了,因而組長教了下分佈式一致性 Session 四種解決辦法,小黑哥給你們整理了一下:
下面小黑哥將會以跟組長對話的形式,講解分佈式一致性 Session 解決辦法。
組長:
若是此時 Tomcat1 Session 存在用戶信息,而 Tomcat2 上沒有存在。
這時若是咱們將 Tomcat1 的 Session 複製到 Tomcat2 上,後面 Nginx 將請求轉發到 Tomcat2 上,因爲 Tomcat2 存在 Session ,這時就不須要再從新登陸了。
架構圖以下:
Tomcat 的 Session 複製的配置,網上有比較多的例子,這裏小黑就再也不貼了,感興趣的同窗能夠自行搜索一下。
小黑:
對的,這種方式挺好啊。Tomcat 就支持這種方式,咱們只須要修改 Tomcat 配置就好,咱們應用代碼都不用修改了。
組長:
說的對,可是這種方式仍是有不少缺點。
第一,Session 複製傳輸須要佔用內網帶寬。
第二,咱們的例子就只有兩臺機器,這個複製性能還能夠。可是假設咱們有 N 臺機器,那麼每次複製都要複製給 N-1 臺機器,若是機器不少,可能會造成網絡風暴,複製性能也會呈指數級降低。
第三, Tomcat 須要保存全部的 Session 數據,這個方案的 Session 存儲在內存中,容易受到機器的總內存的限制。咱們沒辦法經過加機器的方式水平擴展,咱們能作的方式就是加大機器內存。可是機器內存越大,價格真的很貴!!!
因此不推薦使用這種方案。
小黑:
恩,這個方案確實有點不靠譜~
哎,有了!咱們的 Session 裏面其實就是存了用戶的信息,那我如今不存 Tomcat Session 裏,我把信息拿出來,存到瀏覽器的 Cookie 中。
這樣,每一個用戶瀏覽器存儲本身的 Cookie 信息,服務端就不須要存儲,這就解決了 Session 複製方案的缺陷了。
接下來用戶每次請求都把這個 Cookie 給我發過來,我判斷 Cookie 裏面用戶信息不就行了。
架構圖以下:
組長,欣賞看了一下我:
對,你這個方案確實可行。
不過麼,若是用這種方案,首先你要想好加密方案。
用戶信息但是咱們的敏感數據,不能讓別人輕易的竊取或者篡改數據了。
除了這個,這個方案每次請求都要攜帶 Cookie 傳輸,這會佔用外網的帶寬,若是 Cookie 過大,會增大網絡的開銷。
另外,咱們存儲的數據大小,容易受到 Cookie 限制。
因此這種仍是不怎麼經常使用,不過也是一種思路。
我比較推薦下面兩種方案。
組長:
剛纔應該看到了,我只是對 Nginx 的配置作了一些修改,而後這個問題就解決了吧。
其實這是由於我修改 Nginx 默認的負載均衡策略,使用 IP Hash 的方式。
Nginx 會使用請求者的 IP 來作 Hash,而後分發到一臺機器上,這樣能夠保證同一 IP 的請求都落在同一臺 Tomcat 上。
架構圖以下:
上面這種方式咱們使用 Nginx 四層負載均衡方式,其實 Nginx 還能夠作到七層負載均衡方式,也就是使用 Http 協議中的一些業務屬性來作 Hash,常見的有 userId,loginId等等。
架構圖以下:
小黑:
這種方案看起來挺簡單的,咱們只須要修改 Nginx 配置就行了,應用端配置無需改動。
只要請求來源 IP 足夠的隨機,那麼 IP HASH 以後兩臺應用上的流量將會足夠隨機。
另外後面若是兩臺機器扛不住,咱們還能夠水平擴展,再加機器,只要修改 Nginx 配置便可。
組長:
你說的這幾點都很正確!
不過你有沒有想過,像咱們公司這種狀況,全部人的出口的 IP 都是一個。那麼咱們公司的全部請求只會到一臺機器上,那咱們這種狀況等於又變成單點了。
另外若是 Tomcat 重啓,Session 因爲是放置在內存內存中,這一部分的 Session 將會丟失,這就致使這部分用戶將會從新登陸。
最後,若是咱們臨時再加機器,修改完 Nginx 配置,從新啓動以後,Nginx 將會從新計算 Hash 分發請求。
這種狀況就會致使有一部分用戶從新路由到一臺新機器上,因爲沒有 Session,又須要從新登陸了。
不過麼,Tomcat 重啓或者新加機器次數不會不少,因此這個問題也不大,用戶體驗稍差點。
今天的咱們這個問題解決方案就先使用這個。
不事後面咱們仍是改爲下面這種方式。
組長:
上面幾種的方式咱們都是把 Session 存儲在應用內存上,應用機器只要重啓,Session 就會丟失。
爲了這個解決這個問題,咱們將 Session 單獨存起來,保存到 Redis 或者 MySQL 中。
不過因爲 Session 須要過時失效的特性,不須要持久化保存,因此這裏我建議使用 Redis 來保存。
這樣架構就變成下方這樣的:
咱們使用這種方案,上沒有 Session 丟失的風險,固然前提是 Redis 不能宕機。
另外後期若是應用能夠直接水平擴展。
若是後面應用的請求量很大,一臺 Redis 扛不住了,那咱們能夠其實能夠作集羣擴展,根據緩存 Key 作路由。
小黑:
對對,這種方式好~
組長:
你不要高興的太早,咱們使用這個方案須要付出必定的代價的。
首先咱們每次請求都須要調用一次 Redis ,這就增長一次網絡的開銷。
另外,引入 Redis,咱們須要對相應的代碼作出修改,這樣複雜度就變高。
因此說,這個方案有利也有弊,固然對於咱們的場景來講,利大於弊。
小黑:
恩,好像是這樣的。
組長:
好了,這麼晚了,問題解決了,咱們去擼個串,我請客!
小黑:
老大,🐂!
組長拍了拍小黑的腦殼:
我這一頓不是白吃哦,下個星期你把如今方式修改一下,修改爲 Session 集中存儲的方式。
給你一個小提示,可使用 spring-session。
小黑:
好吧,吃人嘴短,下週我研究下。
最後總結一下,當咱們後端 Web 應用擴展到多臺後,咱們就會碰到分佈式一致性 Session 的問題,主流解決方案有四種:
上面四種方案,優先推薦第四種。
固然第四種方案須要必定的開發工做量,前期還沒改造的過程能夠選擇 第三種方案中間過渡。
好了,後面就要使用 Session 後端存儲方案改造這個工程了,後面小黑哥再跟你們分享一下 spring-session。
歡迎關注個人公衆號:程序通事,得到平常乾貨推送。若是您對個人專題內容感興趣,也能夠關注個人博客: studyidea.cn