分布式session-SpringSession的应用

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: Spring Session 提供了一种创建和管理 Servlet HttpSession 的方案,默认使用外置 Redis 存储 Session 数据,解决 Session 共享问题。其主要特性包括:提供 API 和实现来管理用户会话,以中立方式替换应用程序容器中的 HttpSession,简化集群会话支持,并在单个浏览器实例中管理多个用户会话。此外,Spring Session 允许通过 headers 提供会话 ID 以使用 RESTful API。结合 Spring Boot 使用时,可通过配置 Redis 依赖和支持缓存的依赖实现 Session 共享。

springsession的特性

Spring Session提供了一套创建和管理Servlet HttpSession的方案,默认采用外置的Redis来存储Session数据,以此来解决Session共享的问题。

Spring Session提供以下特性:

  • API和用于管理用户会话的实现;
  • 允许以应用程序容器(即Tomcat)中性的方式替换HttpSession;
  • Spring Session 让支持集群会话变得不那么繁琐
  • Spring session支持在单个浏览器实例中管理多个用户的会话。
  • Spring Session 允许在headers 中提供会话ID以使用RESTful API。

实现原理

Spring-Session的实现就是设计一个过滤器SessionRepositoryFilter , SessionRepositoryFilter 会先拦截到请求,将 request 和 response 对象转换成 SessionRepositoryRequestWrapper 和SessionRepositoryResponseWrapper 。后续当第一次调用 request 的getSession方法时,会调用到 SessionRepositoryRequestWrapper 的getSession方法。

如果从request中的属性中查找不到session,再通过cookie拿到sessionid去redis中查找,如果差查不到,就直接创建一个redissession对象,并同步到redis中。将创建销毁session的过程从服务器转移到redis中去。

部分源码

java

代码解读

复制代码

/**
     * HttpServletRequest getSession()实现
     */
    @Override
    public HttpSessionWrapper getSession() {
        return getSession(true);
    }

    @Override
    public HttpSessionWrapper getSession(boolean create) {
        HttpSessionWrapper currentSession = getCurrentSession();
        if (currentSession != null) {
            return currentSession;
        }
        //从当前请求获取sessionId
        String requestedSessionId = getRequestedSessionId();
        if (requestedSessionId != null
                && getAttribute(INVALID_SESSION_ID_ATTR) == null) {
            S session = getSession(requestedSessionId);
            if (session != null) {
                this.requestedSessionIdValid = true;
                currentSession = new HttpSessionWrapper(session, getServletContext());
                currentSession.setNew(false);
                setCurrentSession(currentSession);
                return currentSession;
            }
            else {
                // This is an invalid session id. No need to ask again if
                // request.getSession is invoked for the duration of this request
                if (SESSION_LOGGER.isDebugEnabled()) {
                    SESSION_LOGGER.debug(
                            "No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
                }
                setAttribute(INVALID_SESSION_ID_ATTR, "true");
            }
        }
        if (!create) {
            return null;
        }
        if (SESSION_LOGGER.isDebugEnabled()) {
            SESSION_LOGGER.debug(
                    "A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
                            + SESSION_LOGGER_NAME,
                    new RuntimeException(
                            "For debugging purposes only (not an error)"));
        }
        //为当前请求创建session
        S session = SessionRepositoryFilter.this.sessionRepository.createSession();
        //更新时间
        session.setLastAccessedTime(System.currentTimeMillis());
        //对Spring session 进行包装(包装成HttpSession)
        currentSession = new HttpSessionWrapper(session, getServletContext());
        setCurrentSession(currentSession);
        return currentSession;
    }

    /**
     * 根据sessionId获取session
     */
    private S getSession(String sessionId) {
        S session = SessionRepositoryFilter.this.sessionRepository
                .getSession(sessionId);
        if (session == null) {
            return null;
        }
        session.setLastAccessedTime(System.currentTimeMillis());
        return session;
    }

    /**
     * 从当前请求获取sessionId
     */
    @Override
    public String getRequestedSessionId() {
        return SessionRepositoryFilter.this.httpSessionStrategy
                .getRequestedSessionId(this);
    }

    private void setCurrentSession(HttpSessionWrapper currentSession) {
        if (currentSession == null) {
            removeAttribute(CURRENT_SESSION_ATTR);
        }
        else {
            setAttribute(CURRENT_SESSION_ATTR, currentSession);
        }
    }
    /**
     * 获取当前请求session
     */
    @SuppressWarnings("unchecked")
    private HttpSessionWrapper getCurrentSession() {
        return (HttpSessionWrapper) getAttribute(CURRENT_SESSION_ATTR);
    }

结合SpringBoot的使用

1. 导入maven依赖

java

代码解读

复制代码

  <!--依赖 data-redis的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--不能忘记这个依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!--添加cache的依赖信息-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <!--添加 session的依赖-->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>

        <!--解决spring-session处理缓存时乱码的问题-->

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.69</version>
        </dependency>

2. 编写配置类

我们这里采用使用redis存储session数据实现session共享

properties

代码解读

复制代码

spring:
  # 配置Redis的使用
  redis:
    database: 1 # 所使用的数据库  默认是0
    host: localhost  #所使用的redis的主机地址
    port: 7000  # 端口号  默认是 6379
    password:  # 密码
    timeout: 5000 # 超时时间  5000毫秒
    # 连接池 lettuce 的配置
    lettuce:
      pool:
        max-active: 100
        min-idle: 10
        max-wait: 100000
  # 配置session的相关信息
  session:
    store-type: redis  # 配置存储的类型
    timeout: 3600  # 配置过期时间
    redis:
      flush-mode: on_save # 保存时刷新
      namespace: springSession # 命令空间
server:
  port: 8081
  servlet:
    context-path: /session

3.编写controller进行调用

java

代码解读

复制代码

@RestController
public class SessionController {
    @Value("${server.port}")
    private String port;

    @RequestMapping("/createSession")
    public String createSession(HttpSession httpSession){
        String sessionId=httpSession.getId();
        httpSession.setAttribute("name",port+sessionId);
        httpSession.setAttribute("sname",port+":abc");
        return sessionId+"创建端口号是:"+port+"的应用创建Session,属性是:"+httpSession.getAttribute("name").toString();
    }

    @RequestMapping("/getSession")
    public String getSession(HttpSession httpSession){
        return "访问端口号是:"+port+",获取Session属性是:"+httpSession.getAttribute("name").toString();
    }
}

4. 解决session在redis中存储乱码

前面已经导入fastjson,再加上这个配置类

java

代码解读

复制代码

@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {

    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        // 使用 FastJsonRedisSerializer 来序列化和反序列化redis 的 value的值
        FastJsonRedisSerializer<Object> serializer = new FastJsonRedisSerializer<>(Object.class);
        ParserConfig.getGlobalInstance().addAccept("com.muzz");
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setCharset(StandardCharsets.UTF_8);
        serializer.setFastJsonConfig(fastJsonConfig);
        return serializer;
    }
}

5.打包查看效果

使用maven工具中的package 需要启动两个端口来模拟效果,首先启动一个创建session 进入到jar包打包的位置 ,进入cmd命令行

java

代码解读

复制代码

java -jar xxx.jar --server.port=8081

启动另一个端口访问即可 以下是redis存储的session数据

小结

本篇主要介绍SpringSession的特性,结合springboot中的应用,以及多端口实现session共享的访问,下篇我们探究一下SpringSession的设计结构之妙。


转载来源:https://juejin.cn/post/7233682328840306725

相关文章
|
4月前
|
存储 负载均衡 NoSQL
分布式Session
分布式Session
40 0
|
4月前
|
存储 缓存 负载均衡
分布式系统Session一致性问题
分布式系统Session一致性问题
65 0
|
10月前
|
负载均衡 算法 NoSQL
分布式系列教程(15) - 解决分布式Session一致性问题
分布式系列教程(15) - 解决分布式Session一致性问题
88 0
|
存储 缓存 NoSQL
Shiro 解决分布式 Session
在分布式系统中,会话管理是一个重要的问题。Shiro框架提供了一种解决方案,通过其会话管理组件来处理分布式会话。本文演示通过RedisSessionManager解决分布式会话问题。
82 0
|
7天前
|
存储 NoSQL Java
使用springSession完成分布式session
本文介绍了如何使用 `spring-session` 实现分布式 Session 管理,并提供了将 Session 存储在 Redis 中的具体配置示例。通过配置相关依赖及 Spring 的配置文件,可以轻松实现 Session 的分布式存储。示例中详细展示了所需的 Maven 依赖、Spring 配置及过滤器配置,并给出了启动项目后在 Redis 中查看 Session 数据的方法。
|
4月前
|
存储 前端开发 程序员
|
11月前
|
存储
13JavaWeb基础 - Session技术
13JavaWeb基础 - Session技术
30 0
|
存储 负载均衡 算法
分布式session
1.什么是session HTTP是无状态的,session是一种会话保持技术,目的就是以一种方式来记录http请求之间需要传递、交互的数据。 不是每次http请求都会产生的新的session,而是在服务端手动创建的,例如HttpServletRequest.getSession(true)。 session创建后会返回给客户端sessionID,sessionID会以cookie的形式携带在后续的请求中,直到浏览器关闭,一次会话结束。
55 0
|
NoSQL Redis
基于shiro实现session持久化和分布式共享(3)
基于shiro实现session持久化和分布式共享(3)
179 0
基于shiro实现session持久化和分布式共享(3)
|
存储 缓存 NoSQL
基于shiro实现session持久化和分布式共享(2)
基于shiro实现session持久化和分布式共享(2)
352 0
基于shiro实现session持久化和分布式共享(2)