我發現了一個商城,我尚未登陸,就能夠往購物車中添加商品,加了好幾件後,我準備付款,須要我先去登陸,登陸完以後付款。mysql
如今不少商城,都會要求用戶先去登陸,登陸以後再往購物車中添加商品,這樣用戶、購物車、商品,三個對象之間就有了綁定關係。web
而針對我最開始說的那種狀況,其實就是基於session
作的,客戶端往購物車中添加第一個商品的時候,發送一個請求,服務器收到請求以後,建立session
,而後返回當前session
對應的一個JessionId
,瀏覽器存儲在cookie
中,客戶端往購物車添加第二個商品時,攜帶JessionId
,服務端收到請求後,更新session
。瀏覽器關閉後,cookie
失效,JessionId
也就丟失了,須要從新往購物車中添加商品,默認狀況下,session
有效期爲30
分鐘。redis
在分佈式環境下,session
就會出現問題了,假如服務端部署在兩個服務器A
和B
上。第一次往購物車添加商品時,請求落在了服務器A上,服務器A建立了一個session
,並返回JessionId
,第二次往購物車添加商品時,請求落在了服務器B上,請求攜帶的JesssionId
在服務器B上並不會找到對應的session
。這時候服務器B就會建立一個新的session
,並返回對應的JessionId
,客戶端發現第一次添加的商品丟失了。。。spring
接下來,一塊兒來學習分佈式環境下session
一致性是如何實現的。sql
既然分佈式環境中,一個客戶端的多個請求可能會落在多個服務器上,那麼咱們是否能夠改變策略,直接將session信息存儲在客戶端?能夠的,服務器將session信息直接存儲到cookie中,這樣就保證了session的一致性,可是並不推薦這樣去作,由於將一些信息存儲在cookie中,至關於就把這些信息暴露給了客戶端,存在嚴重的安全隱患。mongodb
缺點:apache
將服務器A的session,複製到服務器B,一樣將服務器B的session也複製到服務器A,這樣兩臺服務器的session就一致了。像tomcat等web容器都支持session複製的功能,在同一個局域網內,一臺服務器的session
會廣播給其餘服務器。瀏覽器
缺點:tomcat
同一個網段內服務器太多,每一個服務器都會去複製session,會形成服務器內存浪費。安全
利用Nginx
服務器的反向代理,將服務器A和服務器B進行代理,而後採用ip_hash
的負載策略,將客戶端和服務器進行綁定,也就是說客戶端A第一次訪問的是服務器B,那麼第二次訪問也必然是服務器B,這樣就不存在session不一致的問題了。
缺點:
若是服務器A宕機了,那麼客戶端A和客戶端B的session就會出現丟失。
這種方式就是將全部服務器的session
進行統一管理,可使用redis
等高性能服務器來集中管理session,並且spring官方提供的spirng-session
就是這樣處理session
的一致性問題。這也是目前企業開發用到的比較多的一種分佈式session
解決方案。
Spring
提供了處理分佈式session的解決方案——Spring Session
。Spring Session
提供了用於管理用戶會話的API和實現。
Spring Session
提供了對redis
,mongodb
,mysql
等經常使用的存儲庫的支持,Spring Session
提供與HttpSession
的透明整合,這意味着開發人員可使用Spring Session支持的實現切換HttpSession
實現。仍是原來的配方,產生了不同的味道!
Spring Session
添加了一個SessionRepositoryFilter
的過濾器,用來修改包裝請求和響應,包裝後的請求爲SessionRepositoryRequestWrapper
,調用getSession()
方法的時候實際上就是調用Spring Session
實現了的session。
Spring Session
使用很是簡單,添加了相關依賴後,直接操做HttpSession
就能夠實現效果。
第一步:添加Spring Session
和 redis
的相關依賴
`<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爲何要存儲三條數據,而不是一條呢!對於session的實現,須要監聽它的建立、過時等事件,redis能夠監聽某個key的變化,當key發生變化時,能夠快速作出相應的處理。
可是Redis中帶有過時的key有兩種方式:
當訪問時發現其過時,會產生過時事件,可是沒法保證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過時事件。