SpringBoot 分布式 Session 共享解决方案

SpringBoot 分布式 Session 共享解决方案

分布式Session一致性?

说白了就是服务器集群Session共享的问题,集群情况下,session保存在各自的服务器的tomcat中,当分发地址至不同服务时,导致sesson取不到,就会产生session共享问题。

Session的作用?

Session 是客户端与服务器通讯会话跟踪技术,服务器与客户端保持整个通讯的会话基本信息。

客户端在第一次访问服务端的时候,服务端会响应一个sessionId并且将它存入到本地cookie中,在之后的访问会将cookie中的sessionId放入到请求头中去访问服务器,如果通过这个sessionid没有找到对应的数据那么服务器会创建一个新的sessionid并且响应给客户端。

分布式 Session 存在的问题?

假设第一次访问服务A生成一个sessionid并且存入cookie中,第二次却访问服务B客户端会在cookie中读取sessionid加入到请求头中,如果在服务B通过sessionid没有找到对应的数据那么它创建一个新的并且将sessionid返回给客户端,这样并不能共享我们的Session无法达到我们想要的目的。

解决方案:

  • 使用cookie来完成(很明显这种不安全的操作并不可靠)

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

  • 利用数据库同步session(效率不高,访问压力大)

  • 使用tomcat内置的session同步(同步可能会产生延迟,集群过多tomcat,session全局复制导致性能下架)

  • 使用token代替session

  • 使用spring-session以及集成好的解决方案,存放在redis中(读写效率高,并可在集群环境下做高可用)

spring-session 实战

启动两个SpringBoot项目端口号分别为 8080、8090 进行测试。

1、项目依赖

// spring boot 与 redis应用基本环境配置
compile 'org.springframework.boot:spring-boot-starter-data-redis:2.1.4.RELEASE'
// spring session 与 redis应用基本环境配置
compile 'org.springframework.session:spring-session-data-redis:2.1.5.RELEASE'

2、测试类

@RestController
@RequestMapping("/test")
public class TestController {
    @Value("${server.port}")
    private Integer projectPort;
    
    @GetMapping("/createSession")
    public String createSession(HttpSession session, String name) {
        session.setAttribute("name", name);
        return "当前项目端口:" + projectPort + " 当前sessionId:" + session.getId() + " 在Session中存入成功!";
    }
    
    @GetMapping("/getSession")
    public String getSession(HttpSession session) {
        return "当前项目端口:" + projectPort + " 当前sessionId:" + session.getId() + "  获取的姓名:" + session.getAttribute("name");
    }
}

3、yml 文件配置

为了演示方便,我配置了两个 yml 文件,只是端口不同。

application.yml

server:
    port: 8080
spring:
  redis:
    host: 127.0.0.1
    port: 6379

application-8090.yml

server:
    port: 8090
spring:
  redis:
    host: 127.0.0.1
    port: 6379

4、启动类

在 Spring boot 的文档中,添加 @EnableRedisHttpSession 来开启 `spring session`支持,配置如下:

@SpringBootApplication
@EnableRedisHttpSession
public class AppRun {
    public static void main(String[] args) {
        SpringApplication.run(AppRun.class, args);
    }
}

这样以来,最简单的 Spring boot + redis 实现 session 共享就完成了,下面进行下测试。

5、测试

首先我们开启两个 tomcat 服务,端口分别为 8080 和 8090。

app-run-1.jpg

启动之后进行访问测试,首先访问 8080 端口的 tomcat

http://localhost:8080/test/createSession

然后访问 8080 端口的 getSession 

http://localhost:8080/test/getSession

然后再访问 8090 端口的 getSession

http://localhost:8090/test/getSession

可见,8080与9090两个服务器返回结果一样,实现了session的共享。

6、redis 查看

查看 redis 时,注意到生成了 3 个key。包含了过期时间,sessionId等。

127.0.0.1:6379> keys *
1) "spring:session:expirations:1557234900000"
2) "spring:session:sessions:expires:70c59a29-e6ef-4d4f-825d-03dae4502686"
3) "spring:session:sessions:70c59a29-e6ef-4d4f-825d-03dae4502686"

127.0.0.1:6379> hkeys spring:session:sessions:70c59a29-e6ef-4d4f-825d-03dae4502686
1) "creationTime"
2) "maxInactiveInterval"
3) "lastAccessedTime"
4) "sessionAttr:name"

QQ20190507-204724.jpg

因为 session 是存储在 redis 中,所以,当我把 8080 和 8090 服务器同时停掉之后,再重新启动,然后刷新浏览器中的响应,session 依然可以获取得到。这就保证了用户在登录后,重新启动服务器 session 不会失效的问题。

@EnableRedisHttpSession 注解

在上面的启动类中,用到了 @EnableRedisHttpSession 的注解,查看源码注意到它有 4 个参数让我们使用。

public @interface EnableRedisHttpSession {
    /**
     * 会话超时(以秒为单位)。 默认情况下,它设置为1800秒(30分钟)
     */
    int maxInactiveIntervalInSeconds() default MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
    
    /**
     * 为 redis key 定义唯一的命名空间,默认前缀是 spring:session:
     */
    String redisNamespace() default RedisOperationsSessionRepository.DEFAULT_NAMESPACE;
    
    /**
     * Redis会话的刷新模式。 默认为 ON_SAVE,另一个是 IMMEDIATE
     */
    RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;
    
    /**
     * 过期会话清理的cron表达式。 默认情况下每分钟运行一次
     */
    String cleanupCron() default RedisHttpSessionConfiguration.DEFAULT_CLEANUP_CRON;
}

比如设置

@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 7200)


Spring Session 的两种刷新模式 RedisFlushMode,参考文章:http://www.ibloger.net/article/3360.html

nginx 集群配置

通过spring boot + redis来实现session的共享非常简单,而且用处也极大,配合nginx进行负载均衡,便能实现分布式的应用了。

例如开启 nginx 集群配置如下:

# 默认使用轮询,
upstream backserver{
    server 127.0.0.1:8080;
    server 127.0.0.1:8090;
}
# 修改server中的local
location / {
    proxy_pass  http://backserver;
    ...
}

Gitee源码

https://gitee.com/liurenkui/springboot-session-demo


未经允许请勿转载:程序喵 » SpringBoot 分布式 Session 共享解决方案

点  赞 (0) 打  赏
分享到: