一口氣說出 4 種分散式一致性 Session 實現方式,面試槓槓的~
阿新 • • 發佈:2020-07-17
## 前言
公司有一個 Web 管理系統,使用 Tomcat 進行部署。由於是後臺管理系統,所有的網頁都需要登入授權之後才能進行相應的操作。
起初這個系統的用的人也不多,為了節省資源,這個系統僅僅只是單機部署。後來隨著用的人越來越多,單機已經有點扛不住了,於是我決定再部署了一臺機器。
這時後端系統有兩臺服務,於是我們使用 Nginx 作為反向代理,整體架構圖如下:
![](https://img2020.cnblogs.com/other/1419561/202007/1419561-20200717084116839-1387896943.jpg)
這個架構圖想必大家應該比較熟悉,現在主流的 Web 系統應該都是這麼部署。
經過一些除錯之後,在一個夜深人靜的晚上,將這套系統部署到了生產。本以為沒有什麼事的,很穩的交給測試小姐姐開始測試。
這一測,出了大問題!測試小姐姐反饋,登入過後,沒過一會又需要登入,操作好幾次都是這樣。
檢查了一下,系統應用,配置什麼也沒問題,那到底哪裡出了問題?
這個時候組長剛準備下班,看到我們這裡有問題,於是過來了看了一下。簡單瞭解的一下基本情況,很快就找到了問題的原因,然後在 Nginx 端修改了下配置,重啟解決了問題。
> 先點後贊,養成習慣~關注公號『程式通事』,快來呀!!
## 分散式一致性 Session
解決完問題,組長坐下解釋了問題原因:**分散式一致性 Session**。
原先我們登入之後將會把使用者登入資訊放在 **Session** 中,使用者每次操作首先先校驗 **Session** 是否存在使用者資訊,如果不存在將會強制讓使用者先去登入。
原先架構的中我們只有一臺應用系統,所有操作都在一臺 Tomcat 上,這當然沒有什麼問題。
但是現在我們部署了兩臺系統,由於 Nginx 使用預設負載均衡策略(**輪詢**),請求將會按照時間順序逐一分發到後端應用上。
也就是說剛開始我們在 Tomcat1 登入之後,使用者資訊放在 Tomcat1 的 **Session** 裡。過了一會,請求又被 Nginx 分發到了 Tomcat2 上,這時 Tomcat2 上 **Session** 裡還沒有使用者資訊,於是又要登入。
另外由於我們系統採用單點登入的方式,Tomcat2 登入之後會將 Tomcat1 登入資訊失效,於是乎等到 Nginx 再把流量分發到 Tomcat1 時,Session 中使用者登入資訊已經失效,又要重新登入。
![](https://img2020.cnblogs.com/other/1419561/202007/1419561-20200717084117007-1955841271.gif)
知道了問題,當然想知道解決辦法了,於是組長教了下分散式一致性 Session 四種解決辦法,小黑哥給大家整理了一下:
![](https://img2020.cnblogs.com/other/1419561/202007/1419561-20200717084117187-1365291817.jpg)
下面小黑哥將會以跟組長對話的形式,講解分散式一致性 **Session** 解決辦法。
## Session 複製
**組長:**
如果此時 Tomcat1 **Session** 存在使用者資訊,而 Tomcat2 上沒有存在。
這時如果我們將 Tomcat1 的 **Session** 複製到 Tomcat2 上,後面 Nginx 將請求轉發到 Tomcat2 上,由於 Tomcat2 存在 **Session** ,這時就不需要再重新登入了。
架構圖如下:
![一致性 Session-Session 複製](https://img2020.cnblogs.com/other/1419561/202007/1419561-20200717084117449-444541995.jpg)
> Tomcat 的 **Session** 複製的配置,網上有比較多的例子,這裡小黑就不再貼了,感興趣的同學可以自行搜尋一下。
**小黑:**
對的,這種方式挺好啊。Tomcat 就支援這種方式,我們只需要修改 Tomcat 配置就好,我們應用程式碼都不用修改了。
**組長:**
說的對,但是這種方式還是有很多缺點。
第一,Session 複製傳輸需要佔用內網頻寬。
第二,我們的例子就只有兩臺機器,這個複製效能還可以。但是假設我們有 **N** 臺機器,那麼每次複製都要複製給 **N-1** 臺機器,如果機器很多,可能會形成網路風暴,複製效能也會呈指數級下降。
第三, Tomcat 需要儲存所有的 **Session** 資料,這個方案的 **Session** 儲存在記憶體中,容易受到機器的總記憶體的限制。我們沒辦法通過加機器的方式**水平擴充套件**,我們能做的方式就是加大機器記憶體。但是機器記憶體越大,價格真的很貴!!!
**所以不推薦使用這種方案。**
## Session 前端儲存
**小黑:**
恩,這個方案確實有點不靠譜~
哎,有了!我們的 Session 裡面其實就是存了使用者的資訊,那我現在不存 Tomcat **Session** 裡,我把資訊拿出來,存到瀏覽器的 **Cookie** 中。
這樣,每個使用者瀏覽器儲存自己的 **Cookie** 資訊,服務端就不需要儲存,這就解決了 Session 複製方案的缺陷了。
接下來使用者每次請求都把這個 **Cookie** 給我發過來,我判斷 **Cookie** 裡面使用者資訊不就好了。
架構圖如下:
![一致性 Session-Session 前端儲存](https://img2020.cnblogs.com/other/1419561/202007/1419561-20200717084117597-794450588.jpg)
**組長,欣賞看了一下我:**
對,你這個方案確實可行。
不過麼,如果用這種方案,首先你要想好加密方案。
使用者資訊可是我們的敏感資料,不能讓別人輕易的竊取或者篡改資料了。
除了這個,這個方案每次請求都要攜帶 **Cookie** 傳輸,這會佔用外網的頻寬,如果 **Cookie** 過大,會增大網路的開銷。
另外,我們儲存的資料大小,容易受到 **Cookie** 限制。
所以這種還是不怎麼常用,不過也是一種思路。
我比較推薦下面兩種方案。
## Session 粘滯(Sticky Sessions)
**組長:**
剛才應該看到了,我只是對 Nginx 的配置做了一些修改,然後這個問題就解決了吧。
其實這是因為我修改 Nginx 預設的負載均衡策略,使用 IP Hash 的方式。
Nginx 會使用請求者的 IP 來做 Hash,然後分發到一臺機器上,這樣可以保證同一 IP 的請求都落在同一臺 Tomcat 上。
架構圖如下:
![Session 粘滯-IP Hash](https://img2020.cnblogs.com/other/1419561/202007/1419561-20200717084117712-899171894.jpg)
上面這種方式我們使用 Nginx 四層負載均衡方式,其實 Nginx 還可以做到七層負載均衡方式,也就是使用 Http 協議中的一些**業務屬性**來做 Hash,常見的有 *userId*,*loginId*等等。
架構圖如下:
![一致性 Session-Session 粘滯-七層](https://img2020.cnblogs.com/other/1419561/202007/1419561-20200717084117822-1862175924.jpg)
**小黑:**
這種方案看起來挺簡單的,我們只需要修改 Nginx 配置就好了,應用端配置無需改動。
只要請求來源 IP **足夠的隨機**,那麼 IP HASH 之後兩臺應用上的流量將會足夠隨機。
另外後面如果兩臺機器扛不住,我們還可以水平擴充套件,再加機器,只要修改 Nginx 配置即可。
**組長:**
你說的這幾點都很正確!
不過你有沒有想過,像我們公司這種情況,所有人的出口的 IP 都是一個。那麼我們公司的所有請求只會到一臺機器上,那我們這種情況等於又變成單點了。
另外如果 Tomcat 重啟,**Session** 由於是放置在記憶體記憶體中,這一部分的 **Session** 將會丟失,這就導致這部分使用者將會重新登入。
最後,如果我們臨時再加機器,修改完 Nginx 配置,重新啟動之後,Nginx 將會重新計算 Hash 分發請求。
這種情況就會導致有一部分使用者重新路由到一臺新機器上,由於沒有 **Session**,又需要重新登入了。
不過麼,Tomcat 重啟或者新加機器次數不會很多,所以這個問題也不大,使用者體驗稍差點。
今天的我們這個問題解決方案就先使用這個。
不過後面我們還是改成下面這種方式。
## 後端集中儲存
**組長:**
上面幾種的方式我們都是把 **Session** 儲存在應用記憶體上,應用機器只要重啟,**Session** 就會丟失。
為了這個解決這個問題,我們將 **Session** 單獨存起來,儲存到 Redis 或者 MySQL 中。
不過由於 **Session** 需要過期失效的特性,不需要持久化儲存,所以這裡我建議使用 Redis 來儲存。
這樣架構就變成下方這樣的:
![一致性 Session-Session 後端儲存](https://img2020.cnblogs.com/other/1419561/202007/1419561-20200717084117952-20947315.jpg)
我們使用這種方案,上沒有 **Session** 丟失的風險,當然前提是 Redis 不能宕機。
另外後期如果應用可以直接水平擴充套件。
如果後面應用的請求量很大,一臺 Redis 扛不住了,那我們可以其實可以做叢集擴充套件,根據快取 Key 做路由。
**小黑:**
對對,這種方式好~
**組長:**
你不要高興的太早,我們使用這個方案需要付出一定的代價的。
首先我們每次請求都需要呼叫一次 Redis ,這就增加一次網路的開銷。
另外,引入 Redis,我們需要對相應的程式碼做出修改,這樣複雜度就變高。
所以說,這個方案有利也有弊,當然對於我們的場景來說,利大於弊。
**小黑:**
恩,好像是這樣的。
**組長:**
好了,這麼晚了,問題解決了,我們去擼個串,我請客!
**小黑:**
老大,