分散式session不一致性怎麼辦?以下這篇文章為大家介紹一下redis中分散式session不一致性的解決方案,希望對大家有幫助!
一、Session有什麼作用?
Session 是用戶端與伺服器通訊會話追蹤技術,伺服器與用戶端保持整個通訊的會話基本資訊。 【相關推薦:Redis影片教學】
客戶端在第一次存取服務端的時候,服務端會回應一個sessionId並且將它存入到本地在Cookie中,在之後的存取會將cookie中的sessionId放入到請求頭中去存取伺服器,
如果透過這個sessionId沒有找到對應的資料,那麼伺服器會建立一個新的sessionId並且會回應給客戶端。
二、分散式Session有什麼問題?
單一伺服器web應用程式中,session資訊只需存在該伺服器中,這是我們前幾年最常接觸的方式
但是近年來隨著分散式系統的流行,單系統已經不能滿足日益增長的百萬級用戶的需求,集群方式部署伺服器已在很多公司運用起來
當高並發量的請求到達服務端的時候通過負載平衡的方式分發到叢集中的某個伺服器,這樣就有可能導致同一個使用者的多次請求被分發到叢集的不同伺服器上,就會出現取不到session資料的情況,於是session的共享就成了一個問題。
三、服務做叢集一般是怎麼樣做的?
四、nginx負載平衡與ribbon負載平衡的差異
五、Session一致性解決方案
#1. session複製(同步)
想法:多個服務端之間相互同步session,這樣每個服務端之間都包含全部的session
優點:服務端支援的功能,應用程式不需要修改代碼
缺點:
#2. 客戶端儲存法
想法:服務端儲存所有使用者的session,記憶體佔用較大,可以將session儲存到瀏覽器cookie中,每個端只要儲存一個使用者的資料了
優點:服務端不需要儲存
缺點:
註:該方案雖然不常用,但確實是一種思路。
3. 反向代理hash一致性
# 想法:服務端為了確保高可用,有多台冗餘,反向代理層能不能做一些事情,讓同一個使用者的請求保證落在一台服務端上呢?
#反向代理層使用使用者的ip來做hash,以保證同一個ip的請求落在同一個服務端上
反向代理程式使用http協定中的某些業務屬性來做hash,例如sid,city_id,user_id等,能夠更靈活的實作hash策略,以確保同一個瀏覽器使用者的請求落在同一個伺服器上
優點:
缺點:
#4.後端統一集中儲存
#優點:總結
保證session一致性的架構設計常見方法:六、案例實戰: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; } }
步驟4:控制器
@RestController @RequestMapping(value = "/user") public class UserController { Map<String, User> 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 "成功退出!!"; } }
步驟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:存取測試##先登入:
http://127.0.0.1:8080/user/login?username=user1&password=user1http://127.0.0.1:8080/user/find/user1七、剖析SpringSession的redis原理
#步驟1:分析SpringSession的redis資料結構 127.0.0.1:6379> 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"
查詢類型
127.0.0.1:6379> type spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b hash
127.0.0.1:6379> hgetall spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b // session的创建时间 1) "creationTime" 2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01|\xc5\xdb\xecu" // sesson的属性,存储了user对象 3) "sessionAttr:d3434f61-4d0a-4687-9070-610bd7790f3b" 4) "\xac\xed\x00\x05sr\x00\x1ecom.ljw.redis.controller.User\x16\"_m\x1b\xa0W\x7f\x02\x00\x03I\x00\x02idL\x00\bpasswordt\x00\x12Ljava/lang/String;L\x00\busernameq\x00~\x00\x01xp\x00\x00\x00\x01t\x00\x05user1q\x00~\x00\x03" //最后的访问时间 5) "lastAccessedTime" 6) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01|\xc5\xe1\xc7\xed" //失效时间 100分钟 7) "maxInactiveInterval" 8) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x17p"
步驟2:分析SpringSession的redis過期策略對於過期數據,一般有三種刪除策略:
定期删除,即每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
redis删除过期数据采用的是懒性删除+定期删除组合策略,也就是数据过期了并不会及时被删除。
但由于redis是单线程,并且redis对删除过期的key优先级很低;如果有大量的过期key,就会出现key已经过期但是未删除。
为了实现 session 过期的及时性,spring session 采用了定时删除+惰性删除的策略。
定时删除
127.0.0.1:6379> type spring:session:expirations:1635413520000 set 127.0.0.1:6379> smembers spring:session:expirations:1635413520000 1) "\xac\xed\x00\x05t\x00,expires:d3434f61-4d0a-4687-9070-610bd7790f3b"
2) "spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b" 3) "spring:session:expirations:1635413520000" 6) "spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b"
惰性删除
127.0.0.1:6379> type spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b string 127.0.0.1:6379> get spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b "" 127.0.0.1:6379> ttl spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b (integer) 3143 127.0.0.1:6379>
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中分散式session不一致性怎麼辦的詳細內容。更多資訊請關注PHP中文網其他相關文章!