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. 环境安装和pycharm中一些基本设置

    一.Pycharm的使用和Python环境 1.python和pycharm的安装 python推荐版本3.7/3.8 pycharm选择社区版本 查看python版本在cmd中输入"pyt ...

  2. OWASP固件安全性测试指南

    OWASP固件安全性测试指南 固件安全评估,英文名称 firmware security testing methodology 简称 FSTM.该指导方法主要是为了安全研究人员.软件开发人员.顾问. ...

  3. 学习笔记:[算法分析]数据结构与算法Python版

    什么是算法分析 对比程序,还是算法? ❖如何对比两个程序? 看起来不同,但解决同一个问题的程序,哪个" 更好"? ❖程序和算法的区别 算法是对问题解决的分步描述 程序则是采用某种编 ...

  4. IDM下载器:站点抓取相关设置介绍

    Internet Download Manager(简称IDM)是一款十分好用资源下载器,它的站点抓取功能不仅可以下载被过滤器指定所需文件,例如一个站点的所有图片,或者一个站点的所有音频,也可以下载站 ...

  5. sublime text3配置javascript运行环境

    步骤一 安装node.js 官网下载链接:node.js 步骤二 Sublime 依次点击 菜单栏 Tools => Build System => New Build System 步骤 ...

  6. 1.Cobaltstrike 安装与简介

    1.Cobaltstrike 安装与简介 一.简介 Cobalt Strike是一款美国Red Team开发的渗透测试神器,常被业界人内称为CS.自去年起, Cobaltstrike升级到3.0版本, ...

  7. 怎么用Iometer测试存储性能

    1.Disk Targets选项栏中选择要测试的磁盘,1 per target of Outstanding I/Os 保持默认即可. 2.在Access Specifications栏中新建测试条件 ...

  8. JDK阅读之Enum

    JDK学习之Enum enum的使用 在没有enum之前如果想要定义一些常量,就会采用如下的方式 假设要定义四个常量表示不同的季节 public class SeasonWithoutEnum { p ...

  9. Python爬虫合集:花6k学习爬虫,终于知道爬虫能干嘛了

    爬虫Ⅰ:爬虫的基础知识 爬虫的基础知识使用实例.应用技巧.基本知识点总结和需要注意事项 爬虫初始: 爬虫: + Request + Scrapy 数据分析+机器学习 + numpy,pandas,ma ...

  10. MySQL重做日志(redo log)

    前面介绍了三种日志:error log.slow log.binlog,这三种都是 Server 层的.今天的 redo log 是 InnoDB引擎专有的日志文件. 为什么要有 redo log 用 ...