分佈式Session的幾種解決方案,你中意哪一種?

我發現了一個商城,我尚未登陸,就能夠往購物車中添加商品,加了好幾件後,我準備付款,須要我先去登陸,登陸完以後付款。mysql

如今不少商城,都會要求用戶先去登陸,登陸以後再往購物車中添加商品,這樣用戶、購物車、商品,三個對象之間就有了綁定關係。web

而針對我最開始說的那種狀況,其實就是基於session作的,客戶端往購物車中添加第一個商品的時候,發送一個請求,服務器收到請求以後,建立session,而後返回當前session對應的一個JessionId,瀏覽器存儲在cookie中,客戶端往購物車添加第二個商品時,攜帶JessionId,服務端收到請求後,更新session。瀏覽器關閉後,cookie失效,JessionId也就丟失了,須要從新往購物車中添加商品,默認狀況下,session有效期爲30分鐘。redis

在分佈式環境下,session就會出現問題了,假如服務端部署在兩個服務器AB上。第一次往購物車添加商品時,請求落在了服務器A上,服務器A建立了一個session,並返回JessionId,第二次往購物車添加商品時,請求落在了服務器B上,請求攜帶的JesssionId在服務器B上並不會找到對應的session。這時候服務器B就會建立一個新的session,並返回對應的JessionId,客戶端發現第一次添加的商品丟失了。。。spring

接下來,一塊兒來學習分佈式環境下session一致性是如何實現的。sql

1、客戶端存儲

既然分佈式環境中,一個客戶端的多個請求可能會落在多個服務器上,那麼咱們是否能夠改變策略,直接將session信息存儲在客戶端?能夠的,服務器將session信息直接存儲到cookie中,這樣就保證了session的一致性,可是並不推薦這樣去作,由於將一些信息存儲在cookie中,至關於就把這些信息暴露給了客戶端,存在嚴重的安全隱患。mongodb

缺點apache

  • 安全性存在問題
  • cookie對於數據類型及數據大小有所限制

2、session複製

將服務器A的session,複製到服務器B,一樣將服務器B的session也複製到服務器A,這樣兩臺服務器的session就一致了。像tomcat等web容器都支持session複製的功能,在同一個局域網內,一臺服務器的session會廣播給其餘服務器。瀏覽器

圖片

缺點tomcat

同一個網段內服務器太多,每一個服務器都會去複製session,會形成服務器內存浪費。安全

3、session黏性

利用Nginx服務器的反向代理,將服務器A和服務器B進行代理,而後採用ip_hash的負載策略,將客戶端和服務器進行綁定,也就是說客戶端A第一次訪問的是服務器B,那麼第二次訪問也必然是服務器B,這樣就不存在session不一致的問題了。

圖片

缺點

若是服務器A宕機了,那麼客戶端A和客戶端B的session就會出現丟失。

4、session集中管理

這種方式就是將全部服務器的session進行統一管理,可使用redis等高性能服務器來集中管理session,並且spring官方提供的spirng-session就是這樣處理session的一致性問題。這也是目前企業開發用到的比較多的一種分佈式session解決方案。

圖片

5、spring-session實戰

Spring提供了處理分佈式session的解決方案——Spring SessionSpring Session提供了用於管理用戶會話的API和實現。

Spring Session提供了對redismongodbmysql等經常使用的存儲庫的支持,Spring Session提供與HttpSession的透明整合,這意味着開發人員可使用Spring Session支持的實現切換HttpSession實現。仍是原來的配方,產生了不同的味道!

圖片

Spring Session添加了一個SessionRepositoryFilter的過濾器,用來修改包裝請求和響應,包裝後的請求爲SessionRepositoryRequestWrapper,調用getSession()方法的時候實際上就是調用Spring Session實現了的session。

Spring Session使用很是簡單,添加了相關依賴後,直接操做HttpSession就能夠實現效果。

第一步:添加Spring Sessionredis的相關依賴

`<dependency>`
 `<groupId>org.springframework.boot</groupId>`
 `<artifactId>spring-boot-starter-web</artifactId>`
`</dependency>`
`<dependency>`
 `<groupId>org.springframework.session</groupId>`
 `<artifactId>spring-session-data-redis</artifactId>`
`</dependency>`
`<dependency>`
 `<groupId>org.springframework.boot</groupId>`
 `<artifactId>spring-boot-starter-data-redis</artifactId>`
`</dependency>`
`<dependency>`
 `<groupId>org.apache.commons</groupId>`
 `<artifactId>commons-pool2</artifactId>`
`</dependency>`

第二步:配置redis相關信息

`spring:`
 `redis:`
 `# redis庫`
 `database: 0`
 `# redis 服務器地址`
 `host: localhost`
 `# redis 端口號`
 `port: 6379`
 `# redis 密碼`
 `password:`
 `# session 使用redis存儲` 
 `session:`
 `store-type: redis`

第三步:項目中使用session

`public String sessionTest(HttpServletRequest request){`
 `HttpSession session = request.getSession();`
 `session.setAttribute("key","value");`
 `return session.getAttribute("key").toString();`
`}`

redis中每一個session存儲了三條信息。

圖片

  • 第一個存儲這個Session的id,是一個Set類型的Redis數據結構。這個k中的最後的1439245080000值是一個時間戳,根據這個Session過時時刻滾動至下一分鐘而計算得出。
  • 第二個用來存儲Session的詳細信息,包括Session的過時時間間隔、最近的訪問時間、attributes等等。這個k的過時時間爲Session的最大過時時間 + 5分鐘。若是默認的最大過時時間爲30分鐘,則這個k的過時時間爲35分鐘。
  • 第三個用來表示Session在Redis中的過時,這個k-v不存儲任何有用數據,只是表示Session過時而設置。這個k在Redis中的過時時間即爲Session的過時時間間隔。

處理一個session爲何要存儲三條數據,而不是一條呢!對於session的實現,須要監聽它的建立、過時等事件,redis能夠監聽某個key的變化,當key發生變化時,能夠快速作出相應的處理。

可是Redis中帶有過時的key有兩種方式:

  • 當訪問時發現其過時
  • Redis後臺逐步查找過時鍵

當訪問時發現其過時,會產生過時事件,可是沒法保證key的過時時間抵達後當即生成過時事件。

spring-session爲了可以及時的產生Session的過時時的過時事件,因此增長了:

spring:session:sessions:expires:726de8fc-c045-481a-986d-f7c4c5851a67`spring:session:expirations:1620393360000`

spring-session中有個定時任務,每一個整分鐘都會查詢相應的spring:session:expirations:整分鐘的時間戳中的過時SessionId,而後再訪問一次這個SessionId,即spring:session:sessions:expires:SessionId,以便可以讓Redis及時的產生key過時事件——即Session過時事件。





圖片

相關文章
相關標籤/搜索