Spring Session解决Session共享
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
- 部署Nginx
Linux(CentOS7)安装Nginx(附简单配置) - Nginx添加配置
 
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>
- 部署Redis
Docker 安装并部署Tomcat、Mysql8、Redis - application.yml添加Redis配置
 
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解决Session共享的更多相关文章
- 使用 StateServer 保存 Session 解决 Session过期,登陆过期问题。
		
使用 StateServer 保存 Session 正常操作情况下Session会无故丢失.因为程序是在不停的被操作,排除Session超时的可能.另外,Session超时时间被设定成60分钟,不会这 ...
 - 使用Spring Session和Redis解决分布式Session跨域共享问题
		
http://blog.csdn.net/xlgen157387/article/details/57406162 使用Spring Session和Redis解决分布式Session跨域共享问题
 - 170222、使用Spring Session和Redis解决分布式Session跨域共享问题
		
使用Spring Session和Redis解决分布式Session跨域共享问题 原创 2017-02-27 徐刘根 Java后端技术 前言 对于分布式使用Nginx+Tomcat实现负载均衡,最常用 ...
 - Spring Session解决分布式Session问题的实现原理
		
使用Spring Session和Redis解决分布式Session跨域共享问题 上一篇介绍了如何使用spring Session和Redis解决分布式Session跨域共享问题,介绍了一个简单的案例 ...
 - Spring Boot2 系列教程(二十八)Spring Boot 整合 Session 共享
		
这篇文章是松哥的原创,但是在第一次发布的时候,忘了标记原创,结果被好多号转发,导致我后来整理的时候自己没法标记原创了.写了几百篇原创技术干货了,有一两篇忘记标记原创进而造成的一点点小小损失也能接受,不 ...
 - 解决session共享问题
		
方法一 使用Nginx让它绑定ip(没有共享所以就没有共享问题了) 配置Nginx upstream backserver { ip_hash; server localhost:8080; serv ...
 - php ecshop 二级域名切换跳转时session不同步,解决session无法共享同步导致无法登陆或者无法退出的问题
		
echshop基础上做了单点登录的 一级域名与二级域名 退出时 清空session 都是一级域名的session 因为二级域名的session是设置在二级域名上的 echshop基础上没有做单点登录的 ...
 - Nginx绑定IP,解决session共享
		
1.Nginx通过负载均衡IP地址固定绑定,解决Session共享 upstream note.java.itcast.cn{ ip_hash; server ...
 - Spring Boot 使用 Redis 共享 Session 代码示例
		
参考资料 博客:spring boot + redis 实现session共享 1. 新建 Maven 工程 我新建 spring-boot-session-redis maven 工程 2. 引入 ...
 
随机推荐
- phpmyadmin远程代码执行漏洞(CVE-2016-5734)
			
简介 环境复现:https://github.com/vulhub/vulhub 线上平台:榆林学院内可使用协会内部的网络安全实验平台 phpMyAdmin是一套开源的.基于Web的MySQL数据库管 ...
 - BT下载器Folx标签功能怎么实现自动的资源分类
			
很多经典的电影作品,比如魔戒三部曲.蜘蛛侠系列.漫威动画系列等,在一个系列中都会包含多个作品.如果使用Folx bt种子下载器自带的电影标签的话,会将这些系列电影都归为"电影"标签 ...
 - 堆的数据结构java
			
public class MaxHeap { private int[] data; private int count; private int capacity; public MaxHeap(i ...
 - 图像分割必备知识点 | Unet详解 理论+ 代码
			
文章转自:微信公众号[机器学习炼丹术].文章转载或者交流联系作者微信:cyx645016617 喜欢的话可以参与文中的讨论.在文章末尾点赞.在看点一下呗. 0 概述 语义分割(Semantic Seg ...
 - Centos7安装Nginx详细步骤
			
前言 Nginx 是一款轻量级的Web 服务器 .反向代理服务器及电子邮件(IMAP/POP3)代理服务器. 常用用途: ✓ 1. 反向代理 ✓ 2. 正向代理 这里我给来2张图,对正向代理与反响代理 ...
 - lambda表达式中无法抛出受检异常!
			
抛出受检异常的时候,我们的接口应该带上throw关键字,但通过lambda表达式实现的Consumer的accept方法并不带有关键字,因此在lambda表达式中不能抛出受检异常必须把它吃掉
 - LeetCode 025 Reverse Nodes in k-Group
			
题目描述:Reverse Nodes in k-Group Given a linked list, reverse the nodes of a linked list k at a time an ...
 - 【常见踩坑】】USB调试安装失败(Installation failed with message INSTALL_CANCELED_BY_USER)
			
[参考]http://www.cnblogs.com/liushilin/p/6553918.html 问题:在USB安装调试(小米手机),出现如下错误 解决:1.小米手机解决办法见参考.登录小米账号 ...
 - CentOS中安装Docker步骤
			
1.安装仓库所需要的软件包 yum install -y yum-utils device-mapper-persistent-data lvm2 2.设置yum加速源 yum-config-mana ...
 - JZOJ2020年10月5日提高B组反思
			
2020年10月5日提高B组反思 T1 考试的时候想简单了 觉得把跟没有攻占的点相连的边留下就可以了 没有考虑到最小 WA&RE 10 T2 没有思路 就直接从中间往后枚举分解处 蜜汁错误 W ...