• 分布式中session共享的解决方案:spring-session


    Session是客户端与服务器通讯会话跟踪技术,是服务器与客户端保持整个通讯的会话基本信息。客户端在第一次访问服务器的时候,服务端会响应一个sessionId并且将它存入到本地的Cookie中,在之后的访问会将Cookie中的sessionId放入到请求头中去访问服务器,如果通过这个sessionId没有找到对应的数据,那么服务器就会创建一个新的sessioinId并且响应给客户端。分布式Session的一致性说白了就是服务器集群Session共享的问题。

    分布式中Session存在的共享问题

    假设客户端第一次访问服务A,服务A响应返回了一个sessionId并且存入了本地Cookie中。第二次不访问服务A了,转去访问服务B。因为客户端中的Cookie中已经存有了sessionId,所以访问服务B的时候,会将sessionId加入到请求头中,而服务B因为通过sessionId没有找到相对应的数据,因此它就会创建一个新的sessionId并且响应返回给客户端。这样就造成了不能共享Session的问题。

    分布式中Session共享问题的解决方案

    1.根据Cookie来完成(不安全)。

    2.使用Nginx的IP绑定策略,同一个IP只能在指定的同一个机器访问(不支持负载均衡)。

    3.利用数据库同步Session(效率不高)。

    4.使用Tomcat内置的Session同步机制(同步可能会产生延迟)。

    5.使用Token代替Session。

    6.使用Spring-Session以及集成好的解决方案,存放在Redis中。

    项目实例场景还原

    启动两个Spring Boot项目,端口号分别是8081,8182。

    在两个项目中分别创建SessionSharedController类。

    @RestController
    public class SessionSharedController {
        @Value("${server.port}")
        private Integer projectPort; // 项目端口
    
        @RequestMapping("/createSession")
        public String createSession(HttpSession session, String name) {
            session.setAttribute("name", name);
            return "【当前项目端口:" + projectPort + "】 【当前sessionId :" + session.getId() + "】";
        }
    
        @RequestMapping("/getSession")
        public String getSession(HttpSession session) {
            return "【当前项目端口:" + projectPort + "】 【当前sessionId :" + session.getId()
                    + "】 【获取的姓名:" + session.getAttribute("name") + "】";
        }
    }

    使用Nginx集群,通过修改nginx.conf配置文件使之支持轮询策略(默认)的负载均衡。

    # 开启轮询策略(默认)的负载均衡
    upstream balanceserver{
        server 127.0.0.1:8081;
        server 127.0.0.1:8082;
    }
    # 将请求转发到负载均衡配置的服务器上
    location / {
        proxy_pass  http://balanceserver;
        index  index.html index.htm;
    }

    我们直接通过轮询机制来访问首先向Session中存入一个姓名。

    访问:http://localhost/createSession?name=yanggb
    得到:【当前项目端口:8081】 【当前sessionId :D5312CBE049C0F486315CF550BFB255C】

    因为我们使用的是默认的轮询策略,因为这次访问的是8081端口,那么下次访问的肯定是8082端口,我们可以直接获取到刚才存入Session的值。

    访问:http://localhost/getSession
    得到:【当前项目端口:8082】 【当前sessionId :D85157E33965BE6D7BB1E1CC0E43208F】 【获取的姓名:null】

    这个时候我们会发现,8082端口中并没有我们存入的值,并且sessionId也是与8081端口不同。先想一想,这个时候我们是8082端口的服务器,但是之前我们是在8081端口中存入了一个姓名,那么我们现在来看看访问8081端口是否能获取到之前存入的姓名yanggb。

    访问:http://localhost/getSession
    得到:【当前项目端口:8081】 【当前sessionId :C5E2061BB03CE8FFE3E9FBDA00CFA28C】 【获取的姓名:null】

    显然,8081端口中也获取不到之前存入的姓名yanggb。如果仔细地观察的话,会发现连sessionId都不一样了。原因是因为,在第二次去访问负载均衡服务器的时候,访问的是8082端口的服务器,这个时候客户端在cookie中获取到的是第一次访问8081端口的服务器时响应返回的sessionId,拿这个sessionId去8082端口的服务器上找是找不到的,因此8082端口就重新创建了一个sessionId并将这个sessionI响应返回给客户端,客户端拿这个sessionId替换掉了之前的8081端口服务器响应返回的sessionId。这样,当第三次访问的是8081端口的服务器的时候,就拿了一个在8081端口的服务器上找不到的sessionId去请求,导致又创建一个新的sessionId。这样就陷入了反复循环的境地,两个服务器永远拿到的是对方生成的sessionId,拿不到自己生成的sessionId。

    解决这两个服务之间Session共享问题的方案:Spring-Session

    Spring提供了一个解决方案:Spring-Session用来解决两个服务之间Session共享的问题。

    要使用Spring-Session,需要在pom.xml中添加相关依赖。

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.47</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- spring session 与redis应用基本环境配置,需要开启redis后才可以使用,不然启动Spring boot会报错 -->
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>

    同时,需要修改application.properties配置文件(本地要开启redis服务)。

    spring.redis.database=0
    spring.redis.host=localhost
    spring.redis.port=6379
    #spring.redis.password=
    spring.redis.jedis.pool.max-active=8
    spring.redis.jedis.pool.max-wait=-1
    spring.redis.jedis.pool.max-idle=8
    spring.redis.jedis.pool.min-idle=8
    spring.redis.timeout=10000

    然后再在代码中添加Session配置类。

    /**
     * 这个类用配置redis服务器的连接
     * maxInactiveIntervalInSeconds为SpringSession的过期时间(单位:秒)
     */
    @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
    public class SessionConfig {
        // 冒号后的值为没有配置文件时,制动装载的默认值
        @Value("${redis.hostname:localhost}")
        private String hostName;
        @Value("${redis.port:6379}")
        private int port;
        // @Value("${redis.password}")
        // private String password;
    
        @Bean
        public JedisConnectionFactory jedisConnectionFactory() {
            RedisStandaloneConfiguration redisStandaloneConfiguration =
                    new RedisStandaloneConfiguration();
            redisStandaloneConfiguration.setHostName(hostName);
            redisStandaloneConfiguration.setPort(port);
            // redisStandaloneConfiguration.setDatabase(0);
            // redisStandaloneConfiguration.setPassword(RedisPassword.of("123456"));
            return new JedisConnectionFactory(redisStandaloneConfiguration);
        }
    
        @Bean
        public StringRedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            return new StringRedisTemplate(redisConnectionFactory);
        }
    }

    初始化Session配置

    /**
     * 初始化Session配置
     */
    public class RedisSessionInitializer extends AbstractHttpSessionApplicationInitializer {
        public RedisSessionInitializer() {
            super(RedisSessionConfig.class);
        }
    }

    这个时候再重新跑一次上面的测试,会发现能够拿到相同的Session信息,也就是实现了Session的共享。

    Spring-Sesion实现的原理

    当Web服务器接收到请求后,请求会进入对应的Filter进行过滤,将原本需要由Web服务器创建会话的过程转交给Spring-Session进行创建。Spring-Session会将原本应该保存在Web服务器内存的Session存放到Redis中。然后Web服务器之间通过连接Redis来共享数据,达到Sesson共享的目的。

    "你离开以后,我遇见过很多女孩,像你的眉,像你的眼,但都不是你。"

  • 相关阅读:
    a标签实现文件下载
    $(obj).index(this)与$(this).index()异同讲解
    jquery attr处理checkbox / select 等表单元素时只能使用一次的坑
    多元素浮动高度不一致导致的参差不齐问题解决方案
    jQuery插件 -- 图片随页面滚动fixed
    setTimeout异步加载
    jquery中html、text、val回调函数
    如何使文件在django自动执行
    Django如何从Model中获取字段名称——verbose_name
    Django在根据models生成数据库表时报错
  • 原文地址:https://www.cnblogs.com/yanggb/p/10886520.html
Copyright © 2020-2023  润新知