• 技术文章 >数据库 >Redis

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

    青灯夜游青灯夜游2021-11-17 10:25:54转载218
    分布式session不一致性怎么办?下面本篇文章给大家介绍一下redis中分布式session不一致性的解决方案,希望对大家有所帮助!

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

    一、Session有什么作用?

    二、分布式Session有什么问题?

    单服务器web应用中,session信息只需存在该服务器中,这是我们前几年最常接触的方式

    但是近几年随着分布式系统的流行,单系统已经不能满足日益增长的百万级用户的需求,集群方式部署服务器已在很多公司运用起来

    当高并发量的请求到达服务端的时候通过负载均衡的方式分发到集群中的某个服务器,这样就有可能导致同一个用户的多次请求被分发到集群的不同服务器上,就会出现取不到session数据的情况,于是session的共享就成了一个问题。

    1.png

    三、服务做集群一般是怎么样做的?

    四、nginx负载均衡和ribbon负载均衡的区别

    五、Session一致性解决方案

    1. session复制(同步)

    2.png

    思路:多个服务端之间相互同步session,这样每个服务端之间都包含全部的session

    优点:服务端支持的功能,应用程序不需要修改代码

    缺点:

    2. 客户端存储法

    3.png

    思路:服务端存储所有用户的session,内存占用较大,可以将session存储到浏览器cookie中,每个端只要存储一个用户的数据了

    优点:服务端不需要存储

    缺点:

    注:该方案虽然不常用,但确实是一种思路。

    3. 反向代理hash一致性

    思路:服务端为了保证高可用,有多台冗余,反向代理层能不能做一些事情,让同一个用户的请求保证落在一台服务端上呢?

    方案一:四层代理hash

    4.png

    反向代理层使用用户的ip来做hash,以保证同一个ip的请求落在同一个服务端上

    方案二:七层代理hash

    5.png

    反向代理使用http协议中的某些业务属性来做hash,例如sid,city_id,user_id等,能够更加灵活的实施hash策略,以保证同一个浏览器用户的请求落在同一个服务器上

    优点:

    缺点:

    session一般是有有效期的,所有不足中的两点,可以认为等同于部分session失效,一般问题不大。

    对于四层hash还是七层hash,个人推荐前者:让专业的软件做专业的事情,反向代理就负责转发,尽量不要引入应用层业务属性,除非不得不这么做(例如,有时候多机房多活需要按照业务属性路由到不同机房的服务器)。

    四层、七层负载均衡的区别

    4. 后端统一集中存储

    6.png

    优点:

    不足:增加了一次网络调用,并且需要修改应用代码

    对于db存储还是cache,个人推荐后者:session读取的频率会很高,数据库压力会比较大。如果有session高可用需求,cache可以做高可用,但大部分情况下session可以丢失,一般也不需要考虑高可用。

    总结

    保证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=user1

    再查询http://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"

    共同点:3个key都是以spring:session:开头的,代表了SpringSession的redis数据。

    查询类型

    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过期策略

    对于过期数据,一般有三种删除策略:

    定时删除

    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"

    7.png

    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中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:redis 分布式
    上一篇:浅析Redis中AOF的原理和缺点 下一篇:Redis中什么是慢查询、订阅模式

    相关文章推荐

    • redis为什么用单线程?为什么那么快?• 聊聊Redis中怎么实现支持几乎所有加锁场景的分布式锁• 一文聊聊Redis中的epoll和文件事件• 总结分享几款实用Redis可视化工具• 一招教你解决在php7中不能加载redis的问题• 浅谈Redis中的字典、哈希算法和ReHash原理

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网