分布式环境下SpringBoot的Session共享方案

问题产生原因

应用服务器集群是当前互联网模式下解决高并发的常用手段,当一台应用服务器的处理能力不足时,不要企图更换配置更高的服务器,对于大型网站而言,不管多么强大的服务器,都满足不了持续增长的业务需求,在这种情况下,更好的做法是增加多台配置较低的应用服务器去分担原来服务器的压力,因为这样可以在较低成本的情况下,使系统的可扩展和可伸缩性更好。
多台应用服务器共同对外提供服务,本质上是为了避免当某一台应用服务器宕机或者故障不能对外提供服务时候,对客户端保持透明。在B/S应用中,服务器通过Session跟踪浏览器回话行为,为了达到这个目的就有了Session共享的问题。

分布式环境下的Session共享

由于需要多台应用服务器对外提供服务并且需要对外界保持透明,那就需要能够使得用户请求可以自动的分发至不同的应用服务器,针对这种场景,诞生了以F5为代表的硬件负载均衡设备和以LVS,Ngnix为代表的软负载软件。它们都能够根据请求流量,将用户请求分发至不同的应用服务器当中。本文主要针对一些使用软负载进行Session共享的场景。

软负载方式 + Session共享存储 在这里插入图片描述

SpringBoot提供的Session共享方式

Spring官方对Spring-Session的解决方案目前主要以下几种方式,其原理大体相同,主要都是通过将Session存储进行集中化,只是存储的途径不同,目前主要有以下几种:

  1. Spring Redis

  2. Spring JDBC

  3. Spring MongoDB

  4. Spring Hazelcast

    本文主要介绍基于Redis和JDBC为例进行说明和演示用法,其它方式可参考官方文档。

1.Spring-Session-Redis

application.properties配置文件

spring.session.store-type=redis  #保存方式
server.servlet.session.timeout=60s  #session超时时间
spring.session.redis.flush-mode=ON_SAVE #session更新策略,有ON_SAVE、IMMEDIATE 前者是SessionRepository save方法的时候刷新,后者是任何更新机会就刷新 
spring.session.redis.namespace=spring:session:babyfinger #redis中key的namespace prefix

spring.redis.database=0
spring.redis.port=6379
spring.redis.host=192.168.105.11
`` 
 @RestController
public class BabyFingerController {
    @RequestMapping
    @ResponseStatus(HttpStatus.OK)
    public String sayHello(HttpSession httpSession , String greeting){
        String storedName = (String) httpSession.getAttribute("greeting");
        if (storedName == null) {
            httpSession.setAttribute("greeting", greeting);
        }
        return "say: " + greeting ;
    }
}

@SpringBootApplication
//@EnableRedisHttpSession  改配置意义和 spring.session.store-type=redis 相同,保留其一即可
public class DistributionSessionApplication {

    public static void main(String[] args) {
        SpringApplication.run(DistributionSessionApplication.class, args);
    }

}

通过浏览器访问:
在这里插入图片描述在这里插入图片描述
可以看到redis中已经存储了访问的 回话信息。对应的session 为一个hash值存储,可以看到里边对应的键值对信息。如果通过Spring-Security配置了用户登录行为,那么我们在用户登录成功后的SecurityContextHolder.getContext().setAuthentication(Authentication authentication) 也会存储在该Session中了。
在这里插入图片描述在这里插入图片描述
在经过一分钟之后,我们再去访问,发现Session已经超时,此时将不能再获取到Session中的数据。
在这里插入图片描述

2. Spring-Session-JDBC

application.properties配置文件更改如下,其中platform可以根据数据的类型进行选择,官方内置的建表语句可在jar包中查到。

server.servlet.session.timeout= 60s
spring.session.jdbc.initialize-schema=ALWAYS
spring.session.jdbc.schema=classpath:org/springframework/session/jdbc/schema-@@platform@@.sql # Path to the SQL file to use to initialize the database schema.
spring.session.jdbc.table-name=SPRING_SESSION # Name of the database table used to store sessions.

在这里插入图片描述

以Spring内置的H2数据库为例[如有配置问题,可参考 [参考1] [[参考2] [参考3]] ,主要有两张表,一张用于存储每一个客户端来的Session请求,另一个存放Session的属性信息。

CREATE TABLE SPRING_SESSION (
	PRIMARY_ID CHAR(36) NOT NULL,
	SESSION_ID CHAR(36) NOT NULL,
	CREATION_TIME BIGINT NOT NULL,
	LAST_ACCESS_TIME BIGINT NOT NULL,
	MAX_INACTIVE_INTERVAL INT NOT NULL,
	EXPIRY_TIME BIGINT NOT NULL,
	PRINCIPAL_NAME VARCHAR(100),
	CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
);

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
	SESSION_PRIMARY_ID CHAR(36) NOT NULL,
	ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
	ATTRIBUTE_BYTES LONGVARBINARY NOT NULL,
	CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
	CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
);

同样的我们也可以在我们的SpringApplication中添加 @EnableJdbcHttpSession 注解来代替配置文件实现session在数据库中的存储。配置后的访问效果如下图所示:

在这里插入图片描述
在这里插入图片描述

SpringBoot下的Session共享原理分析

1 浏览器在首次访问服务器时,服务器端会生成一个SessionId,放入Cookie中返回给浏览器,这样同一个回话请求在下次请求时候就会在请求头部中附带该SessionId在Cookie头部给服务器端那为何 浏览器中 SessionId为 ZmU3MjgwNTItMWM2Mi00NTQ4LWFiOTgtMzA1ZWE5MWE4YTA2,而数据库/reids中记录的id为一个 uuid fe728052-1c62-4548-ab98-305ea91a8a06呢? 其实不难发现base64.decode(ZmU3MjgwNTItMWM2Mi00NTQ4LWFiOTgtMzA1ZWE5MWE4YTA2) = fe728052-1c62-4548-ab98-305ea91a8a06

在这里插入图片描述
在这里插入图片描述
我们分析下

<dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-jdbc</artifactId>
</dependency>

在这里插入图片描述
该包中其实包含的类并不多,其中最关键的就是 JdbcOperationsSessionRepository (redis共享的话是RedisOperationsSessionRepository)类,该类提供了操作Session数据库表的主要方法,我们在 调用 httpSession.setAttribution(key,value)的时候,其实调用的是 HttpSessionAdapter.setAttribution(key,value) , 而该方法内部又会调用到JdbcOperationsSessionRepository 的setAttribution(key,value) 方法。JdbcOperationsSessionRepository 内部有一个 JDBCSession的内部类,该类的构造器中 将 primaryKey设置为 uuid,在初次生成SessionId返回给客户端的时候,会将SessionId进行base64.encode,在收到请求的时候又会将其进行 decode. (具体可以通过SessionRepositoryFilter 进行入手跟踪代码)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Spring Session的原理不是本文的主要介绍对象,感兴趣可以参考[参考1] [参考2]

在这里插入图片描述

共享Session的不足之处

  1. 在并发较高的场合不太建议采用JDBC方式实现session的共享存储,这样数据库会成为最终的性能瓶颈;另外不管使用何种存储方式,都会增加服务器短的存储压力。
  2. 共享Session要求代理必须支持采用Cookie方式在http请求头中增加Session从而使得服务器端可以进行识别,而对于一些无线端原生应用,如手机APP、单页SPA应用,本身没有Session信息,要求端到端是无状态的访问,那么使用Session本身就是一个不太好的方案,这个时候可以考虑采用JWT、OAuth2方式。

本文实例代码可以通过 github进行下载:
https://github.com/zc0604

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在基于Spring Boot的分布式架构中,可以采用以下几种方式来解决分布式环境下的Session共享问题: 1. 使用Session复制:在传统的集群环境中,可以将Session对象复制到所有服务器节点上,使得每个节点都拥有完整的Session数据。这种方式可以通过在负载均衡器后面配置会话复制或使用专门的会话复制技术实现。 2. 使用Session持久化:将Session对象的数据存储到外部共享存储中,如数据库或缓存系统(如Redis)。所有服务器节点都可以从共享存储中读取和写入Session数据。这种方式需要确保共享存储的高可用性和性能。 3. 使用无状态Session:将Session数据从服务器端移至客户端,在每个请求中包含所有必要的会话信息,如使用JWT(JSON Web Token)或类似的机制。这样服务器端就无需存储会话信息,从而实现无状态的分布式架构。 4. 使用分布式缓存:利用分布式缓存系统(如Redis)来存储Session数据,并通过在请求中传递唯一标识符(如Session ID)来进行访问。每个服务器节点都可以从分布式缓存中读取和写入Session数据。 5. 使用第三方解决方案:还可以使用一些第三方解决方案来处理分布式环境下的Session共享问题,如Spring Session、Apache Shiro、Hazelcast等。 选择合适的解决方案取决于具体的业务需求和系统架构,并需要综合考虑性能、可伸缩性、复杂性和一致性等因素。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值