分布式session-SpringSession的应用

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 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

相关文章
|
1月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
43 5
|
2月前
|
人工智能 文字识别 Java
SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?
尼恩,一位拥有20年架构经验的老架构师,通过其深厚的架构功力,成功指导了一位9年经验的网易工程师转型为大模型架构师,薪资逆涨50%,年薪近80W。尼恩的指导不仅帮助这位工程师在一年内成为大模型架构师,还让他管理起了10人团队,产品成功应用于多家大中型企业。尼恩因此决定编写《LLM大模型学习圣经》系列,帮助更多人掌握大模型架构,实现职业跃迁。该系列包括《从0到1吃透Transformer技术底座》、《从0到1精通RAG架构》等,旨在系统化、体系化地讲解大模型技术,助力读者实现“offer直提”。此外,尼恩还分享了多个技术圣经,如《NIO圣经》、《Docker圣经》等,帮助读者深入理解核心技术。
SpringCloud+Python 混合微服务,如何打造AI分布式业务应用的技术底层?
|
3月前
|
存储 NoSQL Java
分布式session-SpringSession的应用
Spring Session 提供了一种创建和管理 Servlet HttpSession 的方案,默认使用外置 Redis 存储 Session 数据,解决了 Session 共享问题。其特性包括:API 及实现用于管理用户会话、以应用容器中性方式替换 HttpSession、简化集群会话支持、管理单个浏览器实例中的多个用户会话以及通过 headers 提供会话 ID 以使用 RESTful API。Spring Session 通过 SessionRepositoryFilter 实现,拦截请求并转换 request 和 response 对象,从而实现 Session 的创建与管理。
分布式session-SpringSession的应用
|
2月前
|
缓存 网络协议 API
分布式系统应用之服务发现!
分布式系统应用之服务发现!
|
3月前
|
存储 运维 应用服务中间件
阿里云分布式存储应用示例
通过阿里云EDAS,您可以轻松部署与管理微服务应用。创建应用时,使用`CreateApplication`接口基于模板生成新应用,并获得包含应用ID在内的成功响应。随后,利用`DeployApplication`接口将应用部署至云端,返回&quot;Success&quot;确认部署成功。当业务调整需下线应用时,调用`ReleaseApplication`接口释放资源。阿里云EDAS简化了应用全生命周期管理,提升了运维效率与可靠性。[相关链接]提供了详细的操作与返回参数说明。
|
3月前
|
Dubbo Java 应用服务中间件
分布式(基础)-RMI简单的应用
分布式(基础)-RMI简单的应用
|
4月前
|
机器学习/深度学习 分布式计算 PyTorch
大规模数据集管理:DataLoader在分布式环境中的应用
【8月更文第29天】随着大数据时代的到来,如何高效地处理和利用大规模数据集成为了许多领域面临的关键挑战之一。本文将探讨如何在分布式环境中使用`DataLoader`来优化大规模数据集的管理与加载过程,并通过具体的代码示例展示其实现方法。
263 1
|
4月前
|
运维 安全 Cloud Native
核心系统转型问题之保障云原生分布式转型中的基础设施和应用层面如何解决
核心系统转型问题之保障云原生分布式转型中的基础设施和应用层面如何解决
|
4月前
|
Kubernetes 安全 云计算
分布式应用的终极革命:Distributionless,告别分布式烦恼!
【8月更文挑战第8天】探讨分布式应用的进化形态——Distributionless,一种使开发者聚焦业务逻辑而非系统细节的理念。借助容器化、云计算与自动化工具的进步,分布式应用的开发与管理变得简易。透过示例展现了使用Bazel构建及Kubernetes部署的流程,预示着Distributionless模式下的应用将更加高效、可靠与安全,引领未来分布式应用的发展趋势。
66 7
|
4月前
|
开发者 云计算 数据库
从桌面跃升至云端的华丽转身:深入解析如何运用WinForms与Azure的强大组合,解锁传统应用向现代化分布式系统演变的秘密,实现性能与安全性的双重飞跃——你不可不知的开发新模式
【8月更文挑战第31天】在数字化转型浪潮中,传统桌面应用面临新挑战。本文探讨如何融合Windows Forms(WinForms)与Microsoft Azure,助力应用向云端转型。通过Azure的虚拟机、容器及无服务器计算,可轻松解决性能瓶颈,满足全球用户需求。文中还提供了连接Azure数据库的示例代码,并介绍了集成Azure Storage和Functions的方法。尽管存在安全性、网络延迟及成本等问题,但合理设计架构可有效应对,帮助开发者构建高效可靠的现代应用。
36 0