分布式session的幾種解決方案,你中意哪種?
點(diǎn)擊上方 Java旅途,選擇 設(shè)為星標(biāo)
優(yōu)質(zhì)文章,每日送達(dá)
我發(fā)現(xiàn)了一個(gè)商城,我還沒(méi)有登錄,就可以往購(gòu)物車(chē)中添加商品,加了好幾件后,我準(zhǔn)備付款,需要我先去登錄,登錄完之后付款。
現(xiàn)在很多商城,都會(huì)要求用戶先去登錄,登錄之后再往購(gòu)物車(chē)中添加商品,這樣用戶、購(gòu)物車(chē)、商品,三個(gè)對(duì)象之間就有了綁定關(guān)系。
而針對(duì)我最開(kāi)始說(shuō)的那種情況,其實(shí)就是基于session做的,客戶端往購(gòu)物車(chē)中添加第一個(gè)商品的時(shí)候,發(fā)送一個(gè)請(qǐng)求,服務(wù)器收到請(qǐng)求之后,創(chuàng)建session,然后返回當(dāng)前session對(duì)應(yīng)的一個(gè)JessionId,瀏覽器存儲(chǔ)在cookie中,客戶端往購(gòu)物車(chē)添加第二個(gè)商品時(shí),攜帶JessionId,服務(wù)端收到請(qǐng)求后,更新session。瀏覽器關(guān)閉后,cookie失效,JessionId也就丟失了,需要重新往購(gòu)物車(chē)中添加商品,默認(rèn)情況下,session有效期為30分鐘。
在分布式環(huán)境下,session就會(huì)出現(xiàn)問(wèn)題了,假如服務(wù)端部署在兩個(gè)服務(wù)器A和B上。第一次往購(gòu)物車(chē)添加商品時(shí),請(qǐng)求落在了服務(wù)器A上,服務(wù)器A創(chuàng)建了一個(gè)session,并返回JessionId,第二次往購(gòu)物車(chē)添加商品時(shí),請(qǐng)求落在了服務(wù)器B上,請(qǐng)求攜帶的JesssionId在服務(wù)器B上并不會(huì)找到對(duì)應(yīng)的session。這時(shí)候服務(wù)器B就會(huì)創(chuàng)建一個(gè)新的session,并返回對(duì)應(yīng)的JessionId,客戶端發(fā)現(xiàn)第一次添加的商品丟失了。。。
接下來(lái),一起來(lái)學(xué)習(xí)分布式環(huán)境下session一致性是如何實(shí)現(xiàn)的。
一、客戶端存儲(chǔ)
既然分布式環(huán)境中,一個(gè)客戶端的多個(gè)請(qǐng)求可能會(huì)落在多個(gè)服務(wù)器上,那么我們是否可以改變策略,直接將session信息存儲(chǔ)在客戶端?可以的,服務(wù)器將session信息直接存儲(chǔ)到cookie中,這樣就保證了session的一致性,但是并不推薦這樣去做,因?yàn)閷⒁恍┬畔⒋鎯?chǔ)在cookie中,相當(dāng)于就把這些信息暴露給了客戶端,存在嚴(yán)重的安全隱患。
缺點(diǎn):
安全性存在問(wèn)題 cookie對(duì)于數(shù)據(jù)類型及數(shù)據(jù)大小有所限制
二、session復(fù)制
將服務(wù)器A的session,復(fù)制到服務(wù)器B,同樣將服務(wù)器B的session也復(fù)制到服務(wù)器A,這樣兩臺(tái)服務(wù)器的session就一致了。像tomcat等web容器都支持session復(fù)制的功能,在同一個(gè)局域網(wǎng)內(nèi),一臺(tái)服務(wù)器的session會(huì)廣播給其他服務(wù)器。

缺點(diǎn):
同一個(gè)網(wǎng)段內(nèi)服務(wù)器太多,每個(gè)服務(wù)器都會(huì)去復(fù)制session,會(huì)造成服務(wù)器內(nèi)存浪費(fèi)。
三、session黏性
利用Nginx服務(wù)器的反向代理,將服務(wù)器A和服務(wù)器B進(jìn)行代理,然后采用ip_hash的負(fù)載策略,將客戶端和服務(wù)器進(jìn)行綁定,也就是說(shuō)客戶端A第一次訪問(wèn)的是服務(wù)器B,那么第二次訪問(wèn)也必然是服務(wù)器B,這樣就不存在session不一致的問(wèn)題了。

缺點(diǎn):
如果服務(wù)器A宕機(jī)了,那么客戶端A和客戶端B的session就會(huì)出現(xiàn)丟失。
四、session集中管理
這種方式就是將所有服務(wù)器的session進(jìn)行統(tǒng)一管理,可以使用redis等高性能服務(wù)器來(lái)集中管理session,而且spring官方提供的spirng-session就是這樣處理session的一致性問(wèn)題。這也是目前企業(yè)開(kāi)發(fā)用到的比較多的一種分布式session解決方案。

五、spring-session實(shí)戰(zhàn)
Spring提供了處理分布式session的解決方案——Spring Session。Spring Session提供了用于管理用戶會(huì)話的API和實(shí)現(xiàn)。
Spring Session提供了對(duì)redis,mongodb,mysql等常用的存儲(chǔ)庫(kù)的支持,Spring Session提供與HttpSession的透明整合,這意味著開(kāi)發(fā)人員可以使用Spring Session支持的實(shí)現(xiàn)切換HttpSession實(shí)現(xiàn)。還是原來(lái)的配方,產(chǎn)生了不一樣的味道!

Spring Session添加了一個(gè)SessionRepositoryFilter的過(guò)濾器,用來(lái)修改包裝請(qǐng)求和響應(yīng),包裝后的請(qǐng)求為SessionRepositoryRequestWrapper,調(diào)用getSession()方法的時(shí)候?qū)嶋H上就是調(diào)用Spring Session實(shí)現(xiàn)了的session。
Spring Session使用非常簡(jiǎn)單,添加了相關(guān)依賴后,直接操作HttpSession就可以實(shí)現(xiàn)效果。
第一步:添加Spring Session和 redis的相關(guān)依賴
<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相關(guān)信息
spring:
redis:
# redis庫(kù)
database: 0
# redis 服務(wù)器地址
host: localhost
# redis 端口號(hào)
port: 6379
# redis 密碼
password:
# session 使用redis存儲(chǔ)
session:
store-type: redis
第三步:項(xiàng)目中使用session
public String sessionTest(HttpServletRequest request){
HttpSession session = request.getSession();
session.setAttribute("key","value");
return session.getAttribute("key").toString();
}
redis中每個(gè)session存儲(chǔ)了三條信息。

第一個(gè)存儲(chǔ)這個(gè)Session的id,是一個(gè)Set類型的Redis數(shù)據(jù)結(jié)構(gòu)。這個(gè)k中的最后的1439245080000值是一個(gè)時(shí)間戳,根據(jù)這個(gè)Session過(guò)期時(shí)刻滾動(dòng)至下一分鐘而計(jì)算得出。
第二個(gè)用來(lái)存儲(chǔ)Session的詳細(xì)信息,包括Session的過(guò)期時(shí)間間隔、最近的訪問(wèn)時(shí)間、attributes等等。這個(gè)k的過(guò)期時(shí)間為Session的最大過(guò)期時(shí)間 + 5分鐘。如果默認(rèn)的最大過(guò)期時(shí)間為30分鐘,則這個(gè)k的過(guò)期時(shí)間為35分鐘。
第三個(gè)用來(lái)表示Session在Redis中的過(guò)期,這個(gè)k-v不存儲(chǔ)任何有用數(shù)據(jù),只是表示Session過(guò)期而設(shè)置。這個(gè)k在Redis中的過(guò)期時(shí)間即為Session的過(guò)期時(shí)間間隔。
處理一個(gè)session為什么要存儲(chǔ)三條數(shù)據(jù),而不是一條呢!對(duì)于session的實(shí)現(xiàn),需要監(jiān)聽(tīng)它的創(chuàng)建、過(guò)期等事件,redis可以監(jiān)聽(tīng)某個(gè)key的變化,當(dāng)key發(fā)生變化時(shí),可以快速做出相應(yīng)的處理。
但是Redis中帶有過(guò)期的key有兩種方式:
當(dāng)訪問(wèn)時(shí)發(fā)現(xiàn)其過(guò)期 Redis后臺(tái)逐步查找過(guò)期鍵
當(dāng)訪問(wèn)時(shí)發(fā)現(xiàn)其過(guò)期,會(huì)產(chǎn)生過(guò)期事件,但是無(wú)法保證key的過(guò)期時(shí)間抵達(dá)后立即生成過(guò)期事件。
spring-session為了能夠及時(shí)的產(chǎn)生Session的過(guò)期時(shí)的過(guò)期事件,所以增加了:
spring:session:sessions:expires:726de8fc-c045-481a-986d-f7c4c5851a67spring:session:expirations:1620393360000
spring-session中有個(gè)定時(shí)任務(wù),每個(gè)整分鐘都會(huì)查詢相應(yīng)的spring:session:expirations:整分鐘的時(shí)間戳中的過(guò)期SessionId,然后再訪問(wèn)一次這個(gè)SessionId,即spring:session:sessions:expires:SessionId,以便能夠讓Redis及時(shí)的產(chǎn)生key過(guò)期事件——即Session過(guò)期事件。
參考
https://www.cnblogs.com/sxw123/p/13803478.html
< END >
往期精選:
老大讓我優(yōu)化數(shù)據(jù)庫(kù),我上來(lái)就分庫(kù)分表,他過(guò)來(lái)就是一jio
