redis中分布式session不一致性怎么辦

分布式Session不一致性怎么辦?下面本篇文章給大家介紹一下redis中分布式session不一致性的解決方案,希望對大家有所幫助!

redis中分布式session不一致性怎么辦

分布式session不一致性解決方案

一、Session有什么作用?

  • Session 是客戶端與服務器通訊會話跟蹤技術,服務器與客戶端保持整個通訊的會話基本信息。【相關推薦:Redis視頻教程

  • 客戶端在第一次訪問服務端的時候,服務端會響應一個sessionId并且將它存入到本地Cookie中,在之后的訪問會將cookie中的sessionId放入到請求頭中去訪問服務器,

  • 如果通過這個sessionId沒有找到對應的數據,那么服務器會創建一個新的sessionId并且響應給客戶端。

二、分布式Session有什么問題?

單服務器web應用中,session信息只需存在該服務器中,這是我們前幾年最常接觸的方式

但是近幾年隨著分布式系統的流行,單系統已經不能滿足日益增長的百萬級用戶的需求,集群方式部署服務器已在很多公司運用起來

當高并發量的請求到達服務端的時候通過負載均衡的方式分發到集群中的某個服務器,這樣就有可能導致同一個用戶的多次請求被分發到集群的不同服務器上,就會出現取不到session數據的情況,于是session的共享就成了一個問題。

redis中分布式session不一致性怎么辦

三、服務做集群一般是怎么樣做的?

  • springBoot項目,那么只要改下端口號啟動幾個,然后用nginx統一做反向代理。
  • SpringCloud微服務項目,那么這個時候,可以使用ribbon本地負載均衡。

四、nginx負載均衡和ribbon負載均衡的區別

  • nginx做負載均衡是服務器端的負載均衡,統一訪問一個地址,根據負載均衡算法訪問決定訪問那一個服務器。
  • ribbon負載均衡,這是本地負載均衡(客戶端負載均衡),把提供服務的客戶端地址都緩存記錄下來,根據本地的算法實現負載均衡。

五、Session一致性解決方案

1. session復制(同步)

redis中分布式session不一致性怎么辦

思路:多個服務端之間相互同步session,這樣每個服務端之間都包含全部的session

優點:服務端支持的功能,應用程序不需要修改代碼

缺點:

  • session的同步需要數據傳輸,占內網帶寬,有時延
  • 所有服務端都包含所有session數據,數據量受內存限制,無法水平擴展

2. 客戶端存儲法?

redis中分布式session不一致性怎么辦

思路:服務端存儲所有用戶的session,內存占用較大,可以將session存儲到瀏覽器cookie中,每個端只要存儲一個用戶的數據了

優點:服務端不需要存儲

缺點:

  • 每次http請求都攜帶session,占外網帶寬
  • 數據存儲在端上,并在網絡傳輸,存在泄漏、篡改、竊取等安全隱患
  • session存儲的數據大小和域名cookie個數都受限制的

注:該方案雖然不常用,但確實是一種思路。

3. 反向代理hash一致性

?思路:服務端為了保證高可用,有多臺冗余,反向代理層能不能做一些事情,讓同一個用戶的請求保證落在一臺服務端上呢?

方案一:四層代理hash

redis中分布式session不一致性怎么辦

反向代理層使用用戶的ip來做hash,以保證同一個ip的請求落在同一個服務端上

方案二:七層代理hash

redis中分布式session不一致性怎么辦

反向代理使用http協議中的某些業務屬性來做hash,例如sid,city_id,user_id等,能夠更加靈活的實施hash策略,以保證同一個瀏覽器用戶的請求落在同一個服務器上

優點:

  • 只需要改nginx配置,不需要修改應用代碼
  • 負載均衡,只要hash屬性是均勻的,多臺服務端的負載是均衡的
  • 可以支持服務端水平擴展(session同步法是不行的,受內存限制)

缺點:

  • 如果服務端重啟,一部分session會丟失,產生業務影響,例如部分用戶重新登錄
  • 如果服務端水平擴展,rehash后session重新分布,也會有一部分用戶路由不到正確的session

session一般是有有效期的,所有不足中的兩點,可以認為等同于部分session失效,一般問題不大。

對于四層hash還是七層hash,個人推薦前者:讓專業的軟件做專業的事情,反向代理就負責轉發,盡量不要引入應用層業務屬性,除非不得不這么做(例如,有時候多機房多活需要按照業務屬性路由到不同機房的服務器)。

Redis視頻教程

4. 后端統一集中存儲

redis中分布式session不一致性怎么辦

優點:

  • 沒有安全隱患
  • 可以水平擴展,數據庫/緩存水平切分即可
  • 服務端重啟或者擴容都不會有session丟失

不足:增加了一次網絡調用,并且需要修改應用代碼

對于db存儲還是cache,個人推薦后者:session讀取的頻率會很高,數據庫壓力會比較大。如果有session高可用需求,cache可以做高可用,但大部分情況下session可以丟失,一般也不需要考慮高可用。

總結

保證session一致性的架構設計常見方法:

  • session同步法:多臺服務端相互同步數據
  • 客戶端存儲法 一個用戶只存儲自己的數據
  • 反向代理hash一致性 四層hash和七層hash都可以做,保證一個用戶的請求落在一臺服務端上
  • 后端統一存儲 服務端重啟和擴容,session也不會丟失(推薦后端cache統一存儲)

六、案例實戰:SpringSession+redis解決分布式session不一致性問題

步驟1:加入SpringSession、redis的依賴包

<dependency> ????<groupid>org.springframework.boot</groupid> ????<artifactid>spring-boot-starter-redis</artifactid> ????<version>1.4.7.RELEASE</version></dependency><dependency> ????<groupid>org.springframework.session</groupid> ????<artifactid>spring-session-data-redis</artifactid></dependency>

步驟2:配置文件

#?為某個包目錄下?設置日志 logging.level.com.ljw=debug  #?設置session的存儲方式,采用redis存儲 spring.session.store-type=redis #?session有效時長為10分鐘 server.servlet.session.timeout=PT10M  ##?Redis?配置 ##?Redis數據庫索引(默認為0) spring.redis.database=0 ##?Redis服務器地址 spring.redis.host=127.0.0.1 ##?Redis服務器連接端口 spring.redis.port=6379 ##?Redis服務器連接密碼(默認為空) spring.redis.password=

步驟3: 配置攔截器

@Configuration public?class?SessionConfig?implements?WebMvcConfigurer?{  ????@Override ????public?void?addInterceptors(InterceptorRegistry?registry)?{ ????????registry.addInterceptor(new?SecurityInterceptor()) ????????????????//排除攔截的2個路徑 ????????????????.excludePathPatterns("/user/login") ????????????????.excludePathPatterns("/user/logout") ????????????????//攔截所有URL路徑 ????????????????.addPathPatterns("/**"); ????} }
@Configuration public?class?SecurityInterceptor?implements?HandlerInterceptor?{ ????@Override ????public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler)?throws?IOException?{ ????????HttpSession?session?=?request.getSession(); ????????//驗證當前session是否存在,存在返回true?true代表能正常處理業務邏輯 ????????if?(session.getAttribute(session.getId())?!=?null){ ????????????log.info("session攔截器,session={},驗證通過",session.getId()); ????????????return?true; ????????} ????????//session不存在,返回false,并提示請重新登錄。 ????????response.setCharacterEncoding("UTF-8"); ????????response.setContentType("application/json;?charset=utf-8"); ????????response.getWriter().write("請登錄!!!!!"); ????????log.info("session攔截器,session={},驗證失敗",session.getId()); ????????return?false; ????} }
  • HandlerInterceptor
    • preHandle:在業務處理器處理請求之前被調用。預處理,可以進行編碼、安全控制、權限校驗等處理;
    • postHandle:在業務處理器處理請求執行完成后,生成視圖之前執行。后處理(調用了Service并返回ModelAndView,但未進行頁面渲染),有機會修改ModelAndView
    • afterCompletion:在DispatcherServlet完全處理完請求后被調用,可用于清理資源等。返回處理(已經渲染了頁面)

步驟4: 控制器

@RestController @RequestMapping(value?=?"/user") public?class?UserController?{  ????Map<string>?userMap?=?new?HashMap();  ????public?UserController()?{ ????????//初始化2個用戶,用于模擬登錄 ????????User?u1=new?User(1,"user1","user1"); ????????userMap.put("user1",u1); ????????User?u2=new?User(2,"user2","user2"); ????????userMap.put("user2",u2); ????}  ????@GetMapping(value?=?"/login") ????public?String?login(String?username,?String?password,?HttpSession?session)?{ ????????//模擬數據庫的查找 ????????User?user?=?this.userMap.get(username); ????????if?(user?!=?null)?{ ????????????if?(!password.equals(user.getPassword()))?{ ????????????????return?"用戶名或密碼錯誤!!!"; ????????????}?else?{ ????????????????session.setAttribute(session.getId(),?user); ????????????????log.info("登錄成功{}",user); ????????????} ????????}?else?{ ????????????return?"用戶名或密碼錯誤!!!"; ????????} ????????return?"登錄成功!!!"; ????}  ????/** ?????*?通過用戶名查找用戶 ?????*/ ????@GetMapping(value?=?"/find/{username}") ????public?User?find(@PathVariable?String?username)?{ ????????User?user=this.userMap.get(username); ????????log.info("通過用戶名={},查找出用戶{}",username,user); ????????return?user; ????}  ????/** ?????*拿當前用戶的session ?????*/ ????@GetMapping(value?=?"/session") ????public?String?session(HttpSession?session)?{ ????????log.info("當前用戶的session={}",session.getId()); ????????return?session.getId(); ????}  ????/** ?????*?退出登錄 ?????*/ ????@GetMapping(value?=?"/logout") ????public?String?logout(HttpSession?session)?{ ????????log.info("退出登錄session={}",session.getId()); ????????session.removeAttribute(session.getId()); ????????return?"成功退出!!"; ????}  }</string>

步驟5: 實體類

@Data public?class?User?implements??Serializable{  ????private?int?id; ????private?String?username; ????private?String?password;  ????public?User(int?id,?String?username,?String?password)?{ ????????this.id?=?id; ????????this.username?=?username; ????????this.password?=?password; ????}  }

步驟6:訪問測試

先登錄:Redis視頻教程

再查詢Redis視頻教程

七、剖析SpringSession的redis原理

步驟1:分析SpringSession的redis數據結構

127.0.0.1:6379&gt;?keys?* 1)?"spring:session:sessions:9889ccfd-f4c9-41e5-b9ab-a77649a7bb6a" 2)?"spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b" 3)?"spring:session:expirations:1635413520000" 4)?"spring:session:sessions:expires:9889ccfd-f4c9-41e5-b9ab-a77649a7bb6a" 5)?"spring:session:expirations:1635412980000" 6)?"spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b"

共同點:3個key都是以spring:session:開頭的,代表了SpringSession的redis數據。

查詢類型

127.0.0.1:6379&gt;?type?spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b hash
127.0.0.1:6379&gt;?hgetall?spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b //?session的創建時間 1)?"creationTime" 2)?"xacxedx00x05srx00x0ejava.lang.Long;x8bxe4x90xccx8f#xdfx02x00x01Jx00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x01|xc5xdbxecu" //?sesson的屬性,存儲了user對象 3)?"sessionAttr:d3434f61-4d0a-4687-9070-610bd7790f3b" 4)?"xacxedx00x05srx00x1ecom.ljw.redis.controller.Userx16"_mx1bxa0Wx7fx02x00x03Ix00x02idLx00bpasswordtx00x12Ljava/lang/String;Lx00busernameqx00~x00x01xpx00x00x00x01tx00x05user1qx00~x00x03" //最后的訪問時間 5)?"lastAccessedTime" 6)?"xacxedx00x05srx00x0ejava.lang.Long;x8bxe4x90xccx8f#xdfx02x00x01Jx00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x01|xc5xe1xc7xed" //失效時間?100分鐘 7)?"maxInactiveInterval" 8)?"xacxedx00x05srx00x11java.lang.Integerx12xe2xa0xa4xf7x81x878x02x00x01Ix00x05valuexrx00x10java.lang.Numberx86xacx95x1dx0bx94xe0x8bx02x00x00xpx00x00x17p"

步驟2:分析SpringSession的redis過期策略

對于過期數據,一般有三種刪除策略:

  • 定時刪除,即在設置鍵的過期時間的同時,創建一個定時器, 當鍵的過期時間到來時,立即刪除。

  • 惰性刪除,即在訪問鍵的時候,判斷鍵是否過期,過期則刪除,否則返回該鍵值。

  • 定期刪除,即每隔一段時間,程序就對數據庫進行一次檢查,刪除里面的過期鍵。至于要刪除多少過期鍵,以及要檢查多少個數據庫,則由算法決定。

  • redis刪除過期數據采用的是懶性刪除+定期刪除組合策略,也就是數據過期了并不會及時被刪除。

  • 但由于redis是單線程,并且redis對刪除過期的key優先級很低;如果有大量的過期key,就會出現key已經過期但是未刪除。

  • 為了實現 session 過期的及時性,spring session 采用了定時刪除+惰性刪除的策略。

定時刪除

127.0.0.1:6379&gt;?type?spring:session:expirations:1635413520000 set 127.0.0.1:6379&gt;?smembers??spring:session:expirations:1635413520000 1)?"xacxedx00x05tx00,expires:d3434f61-4d0a-4687-9070-610bd7790f3b"

redis中分布式session不一致性怎么辦

2)?"spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b"? 3)?"spring:session:expirations:1635413520000"? 6)?"spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b"
  • 1635412980000 是時間戳,等于 2021-10-28 17:23:00,即是該可以在這個時刻過期
  • springsession 定時(1分鐘)輪詢,刪除spring:session:expirations:[?] 的過期成員元素,例如:spring:session:expirations:1635413520000
  • springsesion 定時檢測超時的key的值,根據值刪除seesion,例如key:spring:session:expirations:1635413520000,值為(sessionId):d3434f61-4d0a-4687-9070-610bd7790f3b的seesion

惰性刪除

127.0.0.1:6379&gt;?type?spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b string 127.0.0.1:6379&gt;?get?spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b "" 127.0.0.1:6379&gt;?ttl?spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b (integer)?3143 127.0.0.1:6379&gt;
  • 訪問 spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b的時候,判斷key是否過期,過期則刪除,否則返回改進的值。
  • 例如 訪問spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b的時候,判斷 ttl 是否過期,過期就直接刪除
2)?"spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b"? 3)?"spring:session:expirations:1635413520000"? 6)?"spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b"

更多編程相關知識,請訪問:Redis視頻教程!!

以上就是

? 版權聲明
THE END
喜歡就支持一下吧
點贊13 分享