Designed by 77
加载资源 ......
感谢 ♥ 作者
先不感谢了

分布式中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共享的目的。

 

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

posted @ 2019-05-20 00:39  yanggb  阅读(1600)  评论(1编辑  收藏  举报