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

我發現了一個商城,我還沒有登錄,就可以往購物車中添加商品,加了好幾件後,我準備付款,需要我先去登錄,登錄完之後付款。

現在很多商城,都會要求用戶先去登錄,登錄之後再往購物車中添加商品,這樣用戶、購物車、商品,三個對象之間就有了綁定關係。

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

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

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

一、客戶端存儲

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

缺點

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

二、session複製

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

缺點

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

三、session黏性

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

缺點

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

四、session集中管理

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

五、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過期事件。

參考

https://www.cnblogs.com/sxw123/p/13803478.html

點關注、不迷路

如果覺得文章不錯,歡迎關注、點贊、收藏,你們的支持是我創作的動力,感謝大家。

如果文章寫的有問題,請不要吝惜文筆,歡迎留言指出,我會及時覈查修改。

如果你還想看到更多別的東西,可以微信搜索「Java旅途」進行關注。回覆“手冊”領取Java面試手冊!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章