1. 分布式Session共享

  在分布式集群部署环境下,使用Session存储用户信息,往往出现Session不能共享问题。

  例如:服务集群部署后,分为服务A和服务B,当用户登录时负载到服务A登录成功返回用户Session存到本地Cookie中,下一次操作时从Cookie中获取session添加到请求头并负载到服务B,服务B通过Session Id无法获取用户信息,则返回新的Sssion,这样就出现了Session不共享问题。

2. 常见解决方案

  • 使用Cookie存储用户信息(不推荐,极其不安全)
  • 通过Tomcat内置的Session同步机制(不推荐,可能出现延迟)
  • 使用数据库同步Session(不推荐,效率低)
  • 使用Nginx的ip_hash策略(可用,同一个ip只能负载到同一个server,消失负载均衡功能)
  • 使用token代替Session(推荐,前后端分离/微服务首选)
  • 使用Spring Session实现Session共享(推荐,单体应用集群部署可用)

      以Spring Session将Session存储在Redis中实现Session共享为例。

3. 演示问题

  • 创建Maven工程

  • 修改pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.c3stones</groupId>
<artifactId>spring-session-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-session-demo</name>
<description>Spring Boot Session Demo</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath />
</parent> <properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>
  • 创建Controller
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; /**
* 用户Controller
*
* @author CL
*
*/
@RestController
public class UserController { private static Logger logger = LoggerFactory.getLogger(UserController.class); @Value("${server.port}")
private Integer port; /**
* 模拟登录
*
* @param session HttpSession
* @param username 用户名
* @return
*/
@RequestMapping(value = "/login")
public String login(HttpSession session, String username) {
session.setAttribute("username", username);
String msg = String.format("用户登录的当前服务端口:%s,用户名称为:%s,Session ID为:%s", port, username, session.getId());
logger.info(msg);
return msg;
} /**
* 获取用户信息
*
* @param session HttpSession
* @param username 用户名
* @return
*/
@RequestMapping(value = "/getUserInfo")
public String getUserInfo(HttpSession session) {
String username = (String) session.getAttribute("username");
String msg = String.format("获取到的当前服务端口:%s,用户名称为:%s,Session ID为:%s", port, username, session.getId());
logger.info(msg);
return msg;
} }
  • 创建启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; /**
* 启动类
*
* @author CL
*
*/
@SpringBootApplication
public class Application { public static void main(String[] args) {
SpringApplication.run(Application.class, args);
} }
  • 创建配置文件application.yml
server:
port: ${PORT:8080}
  • 打jar包
#CMD进入项目根路径执行命令
mvn clean install
upstream backend {
server 192.168.0.100:8081 weight=1;
server 192.168.0.100:8082 weight=1;
} server {
listen 8080;
server_name 192.168.0.100; location / {
proxy_pass http://backend;
}
}
  • 上传至服务器并启动
#启动Nginx
/usr/local/nginx/sbin/nginx #启动服务,端口8081
nohup java -jar -DPORT=8081 spring-session-demo-0.0.1-SNAPSHOT & #启动服务,端口8082
nohup java -jar -DPORT=8082 spring-session-demo-0.0.1-SNAPSHOT &
  • 测试

      (可选)本地Hosts文件配置域名映射:192.168.0.100 www.c3stones.com

    模拟登录,生成Session:



    获取用户信息:





      从返回可以看出三次调用返回的Session Id均不一致。

      使用Nginx默认轮询策略,第一次登录负载到8081端口服务上,生成Session,浏览器缓存Session,第二次获取用户信息,浏览器将8081生成的Session绑定到请求头,Nginx负载到8082服务上,8082服务找不到该Session信息,重新生成Session,浏览器将新生成的Session替换之前保存的Session,重复循环,造成用户登录后,两个服务均无用户信息。

4. Spring Session概述

  Spring Session是Spring家族中的一个子项目,Spring Session提供了用于管理用户会话信息的API和实现。它把Servlet容器实现的HttpSession替换为Spring Session,专注于解决Session管理问题,Session信息存储在Redis中,可简单快速的集成到应用中;

  Spring Session官网地址:https://spring.io/projects/spring-session

  Spring Session的特性:

    a. 提供用户Session管理的API和实现;

    b. 提供HttpSession,取代web容器的Session;

    c. 支持集群的Session处理,解决集群下的Session共享问题;

5. 解决问题,实现Session共享

  • 修改pom.xml文件,添加配置
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.c3stones</groupId>
<artifactId>spring-session-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-session-demo</name>
<description>Spring Boot Session Demo</description> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath />
</parent> <properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>
server:
port: ${PORT:8080} spring:
redis:
database: 0
host: 192.168.0.100
port: 6379
password: 123456
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000
  • 添加Session配置类
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; /**
* Session配置类
*
* @EnableRedisHttpSession:启用支持Redis存储Session</br>
* maxInactiveIntervalInSeconds:Session最大过期时间
*
* @author CL
*
*/
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig { @Value("${spring.redis.database}")
private Integer database; @Value("${spring.redis.host}")
private String host; @Value("${spring.redis.port}")
private Integer port; @Value("${spring.redis.password}")
private String password; /**
* 注入Redis连接工厂
*
* @return
*/
@Bean
public JedisConnectionFactory connectionFactory() {
RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
configuration.setHostName(host);
configuration.setPort(port);
configuration.setPassword(password);
configuration.setDatabase(database);
return new JedisConnectionFactory(configuration);
} }
  • 添加Session初始化类
import org.springframework.session.web.context.AbstractHttpSessionApplicationInitializer;

/**
* Session初始化,向Servlet容器中添加SpringSessionRepositoryFilter
*
* @author CL
*
*/
public class SessionInitializer extends AbstractHttpSessionApplicationInitializer { public SessionInitializer() {
super(SessionConfig.class);
} }
  • 添加线程池配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /**
* 线程池配置
*
* @author CL
*
*/
@Configuration
public class ThreadPoolConfig { /**
* Spring Session任务执行器配置
*
* @return
*/
@Bean
public ThreadPoolTaskExecutor springSessionRedisTaskExecutor() {
ThreadPoolTaskExecutor springSessionRedisTaskExecutor = new ThreadPoolTaskExecutor();
springSessionRedisTaskExecutor.setCorePoolSize(8);
springSessionRedisTaskExecutor.setMaxPoolSize(16);
springSessionRedisTaskExecutor.setKeepAliveSeconds(10);
springSessionRedisTaskExecutor.setQueueCapacity(1000);
springSessionRedisTaskExecutor.setThreadNamePrefix("spring-session-thread:");
return springSessionRedisTaskExecutor;
}
}
  • 重新打包,部署
  • 测试

    模拟登录,生成Session:



    获取用户信息:





      从返回可以看出三次调用返回的Session Id一致,已经实现了Session共享问题。
  • 查看Redis



      a.Spring Session定时任务触发Session过期,数据类型:Set

        Key:spring:session:expirations:XXXXX

      b.存储Session,数据类型:Hash

        Key:spring:session:sessions:XXXXXXX

      c. Redis TTL触发Session过期,数据类型:String

        Key:spring:session:sessions:expires:XXXXX

6. 实现原理

  当Web服务器接收到http请求后,请求进入SpringSessionRepositoryFilter,将原本需要由Web服务器创建会话的过程转交给Spring Session进行创建,本来创建的会话保存在Web服务器内存中,通过Spring Session创建的会话信息保存第三方的服务中,如:Redis、Mysql等。Web服务器之间通过连接第三方服务来共享数据,实现Session共享!

7. 项目地址

  spring-session-demo

Spring Session解决Session共享的更多相关文章

  1. 使用 StateServer 保存 Session 解决 Session过期,登陆过期问题。

    使用 StateServer 保存 Session 正常操作情况下Session会无故丢失.因为程序是在不停的被操作,排除Session超时的可能.另外,Session超时时间被设定成60分钟,不会这 ...

  2. 使用Spring Session和Redis解决分布式Session跨域共享问题

    http://blog.csdn.net/xlgen157387/article/details/57406162 使用Spring Session和Redis解决分布式Session跨域共享问题

  3. 170222、使用Spring Session和Redis解决分布式Session跨域共享问题

    使用Spring Session和Redis解决分布式Session跨域共享问题 原创 2017-02-27 徐刘根 Java后端技术 前言 对于分布式使用Nginx+Tomcat实现负载均衡,最常用 ...

  4. Spring Session解决分布式Session问题的实现原理

    使用Spring Session和Redis解决分布式Session跨域共享问题 上一篇介绍了如何使用spring Session和Redis解决分布式Session跨域共享问题,介绍了一个简单的案例 ...

  5. Spring Boot2 系列教程(二十八)Spring Boot 整合 Session 共享

    这篇文章是松哥的原创,但是在第一次发布的时候,忘了标记原创,结果被好多号转发,导致我后来整理的时候自己没法标记原创了.写了几百篇原创技术干货了,有一两篇忘记标记原创进而造成的一点点小小损失也能接受,不 ...

  6. 解决session共享问题

    方法一 使用Nginx让它绑定ip(没有共享所以就没有共享问题了) 配置Nginx upstream backserver { ip_hash; server localhost:8080; serv ...

  7. php ecshop 二级域名切换跳转时session不同步,解决session无法共享同步导致无法登陆或者无法退出的问题

    echshop基础上做了单点登录的 一级域名与二级域名 退出时 清空session 都是一级域名的session 因为二级域名的session是设置在二级域名上的 echshop基础上没有做单点登录的 ...

  8. Nginx绑定IP,解决session共享

    1.Nginx通过负载均衡IP地址固定绑定,解决Session共享           upstream note.java.itcast.cn{    ip_hash;        server ...

  9. Spring Boot 使用 Redis 共享 Session 代码示例

    参考资料 博客:spring boot + redis 实现session共享 1. 新建 Maven 工程 我新建 spring-boot-session-redis maven 工程 2. 引入 ...

随机推荐

  1. [原题复现]2019上海大学生WEB-Decade(无参数RCE、Fuzz)

    简介  原题复现:  考察知识点:无参数命令执行.Fuzz  线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使用信安协会内部的CTF训练平台找到此题 环境复现 ...

  2. SQL注入实战新手教程

    本文章仅用于网络安全交流学习,严禁用于非法用途,否则后果自负 一.如何批量找SQL注入(工具+资源都打包):http://www.liuwx.cn/post-149.html 1.SQL注入点搜索关键 ...

  3. How to realize one's ambition

    Work Overtime Can it work? To some extent, it parhaps works very well. What if you do little job and ...

  4. H5系列之svg

    svg呢,跟canvas其实都是用来画图的,只不过svg画的图,是矢量图,矢量图有个特点是,放大缩小,不会失真,canvas呢,是逐像素进行渲染的. 也就是,他依赖分辨率,会根据分辨率来决定图形是否清 ...

  5. 5w 字 | 172 图 | 超级赛亚级 Spring Cloud 实战

    一.PassJava 项目简介 PassJava-Learning 项目是 PassJava(佳必过)项目的学习教程.对架构.业务.技术要点进行讲解. PassJava 是一款 Java 面试刷题 的 ...

  6. P4771 八百标兵奔北坡

    观察题目中关于北边的定义,发现是以当前点为顶点,向上的倒三角(自己想想为什么). 然后就可以直接 DP 了,令 \(f_{i,j}\) 表示点 \(\left(i,j\right)\) 的答案. \[ ...

  7. flink:JobGraph生成过程分析

    1.JobGraph是由StreamGraph转换而来,当client将StreamGraph提交后,job启动前会先完成转换,统一的转换入口如下: 2.StreamingJobGraphGenera ...

  8. mq checkpoint文件

    记录comitlog,consumeQueue,Index文件的刷盘时间点,文件固定长度4k,其中只用该文件的24个字节,其存储格式: 8字节physicMsgtimestamp+8字节logicsM ...

  9. [Docker]Docker与Linux ip_forward数据包转发

    背景 今天在一台新虚拟机上需要临时启动一个consul服务,安装Docker后使用docker启动,但是在执行启动命令后发现docker有一个警告: WARNING: IPv4 forwarding ...

  10. Java多线程中的wait/notify通信模式

    前言 最近在看一些JUC下的源码,更加意识到想要学好Java多线程,基础是关键,比如想要学好ReentranLock源码,就得掌握好AQS源码,而AQS源码中又有很多Java多线程经典的一些应用:再比 ...