• SpringSession入门


    Session会话管理概述

    1.1 Web中的Session和Cookie回顾

    1.1.1 Session机制

    由于HTTP协议是无状态的协议,一次浏览器和服务器的交互过程就是:

    浏览器:你好吗?

    服务器:很好!

    这就是一次会话,对话完成后,这次会话就结束了,服务器端并不能记住这个人,下次再对话时,服务器端并不知道是上一次的这个人,所以服务端需要记录用户的状态时,就需要用某种机制来识别具体的用户,这个机制就是Session。

    服务端如何识别特定的客户?

    这个时候需要使用Cookie。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。

    实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session时,服务端会在HTTP协议中向客户端 Cookie 中记录一个Session ID,以后每次请求把这个会话ID发送到服务器,这样服务端就知道客户端是谁了。

    1.1.3 url重写

    那么如果客户端的浏览器禁用了 Cookie 怎么办?

    一般这种情况下,会使用一种叫做URL重写的技术来进行session会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sessionId=xxxxx 这样的参数,服务端据此来识别客户端是谁

    1.2 Session会话管理及带来的问题

    ​ 在Web项目开发中,Session会话管理是一个很重要的部分,用于存储与记录用户的状态或相关的数据。

    ​ 通常情况下session交由容器(tomcat)来负责存储和管理,但是如果项目部署在多台tomcat中,则session管理存在很大的问题

    • 多台tomcat之间无法共享session,比如用户在tomcat A服务器上已经登录了,但当负载均衡跳转到tomcat B时,由于tomcat B服务器并没有用户的登录信息,session就失效了,用户就退出了登录

    • 一旦tomcat容器关闭或重启也会导致session会话失效

    ​ 因此如果项目部署在多台tomcat中,就需要解决session共享的问题

    1.3 Session会话共享方案

    • 第一种是使用容器扩展插件来实现,比如基于Tomcat的tomcat-redis-session-manager插件,基于Jetty的jetty-session-redis插件、memcached-session-manager插件;这个方案的好处是对项目来说是透明的,无需改动代码,但是由于过于依赖容器,一旦容器升级或者更换意味着又得重新配置

    ​ 其实底层是,复制session到其它服务器,所以会有一定的延迟,也不能部署太多的服务器。

    • 第二种是使用Nginx负载均衡的ip_hash策略实现用户每次访问都绑定到同一台具体的后台tomcat服务器实现session总是存在

    ​ 这种方案的局限性是ip不能变,如果手机从北京跳到河北,那么ip会发生变化;

    ​ 另外负载均衡的时候,如果某一台服务器发生故障,那么会重新定位,也会跳转到别的机器。

    • 第三种是自己写一套Session会话管理的工具类,在需要使用会话的时候都从自己的工具类中获取,而工具类后端存储可以放到Redis中,这个方案灵活性很好,但开发需要一些额外的时间。

    • 第四种是使用框架的会话管理工具,也就是我们要介绍的Spring session,这个方案既不依赖tomcat容器,又不需要改动代码,由Spring session框架为我们提供,可以说是目前非常完美的session共享解决方案

    Spring Session入门

    2.1 Spring Session简介

    ​ Spring Session 是Spring家族中的一个子项目,它提供一组API和实现,用于管理用户的session信息

    它把servlet容器实现的httpSession替换为spring-session,专注于解决 session管理问题,Session信息存储在Redis中,可简单快速且无缝的集成到我们的应用中;

    官网:https://spring.io/

    Spring Session的特性

    • 提供用户session管理的API和实现

    • 提供HttpSession,以中立的方式取代web容器的session,比如tomcat中的session

    • 支持集群的session处理,不必绑定到具体的web容器去解决集群下的session共享问题

    2.2 测试代码

    image-20210317095618797

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--springboot与Redis集成的起步依赖 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <!--spring session 将数据存入Redis的一个依赖 -->
            <dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-data-redis</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </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>
    

    测试代码

    /**
     * @author WGR
     * @create 2021/3/16 -- 22:07
     */
    
    @RestController
    public class HelloController {
    
        @RequestMapping("/set")
        public Object set(HttpSession session){
            session.setAttribute("myKey","我的Session数据!");
            return "Session设置成功";
        }
    
        @RequestMapping("/get")
        public Object get(HttpSession session){
            String data= (String) session.getAttribute("myKey");
            return data;
        }
    }
    
    
    spring:
      redis:
        host: 192.168.1.104
        port: 6379
      session:
        store-type: redis
    server:
      port: 8081
      servlet:
        context-path: /session
    

    Spring Session常见的应用场景

    同域名下相同项目(集群环境)实现Session共享

    同一个项目,部署了多台tomcat,这就是典型的集群。我们的入门案例就属于这种应用场景,只不过在实际开发的过程中,我们如果存在了tomcat集群,那么肯定会使用nginx进行负载均衡,那么这种情况下我们该如何处理。直接上面的代码就可以,不需要额外的配置

    image-20210316222828966

    同域名下相同项目(集群环境)实现Session共享

    image-20210316224652919

    我们通过浏览器提供的开发人员工具可以发现,这两个请求的cookie的路径(path)不一致,虽然我们已经加了Spring Session共享机制,但是后台服务器认为这是两个不同的会话(session),可以通过Redis客户端工具(Redis Destop Mananger)查看,先清空,然后访问,发现是维护了两个不同的session,所以不能实现共享。

    解决:

    /**
     * @author WGR
     * @create 2021/3/16 -- 22:49
     */
    @EnableRedisHttpSession
    @Configuration
    public class SessionConfig {
    
        @Bean
        public CookieSerializer cookieSerializer() {
            DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
            cookieSerializer.setCookiePath("/");
            return cookieSerializer;
        }
    
        @Bean
        public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
            return new GenericJackson2JsonRedisSerializer();
        }
    }
    
    

    image-20210316225518862

    同根域名不同二级子域名下的项目实现Session共享

    image-20210316230123233

    我们通过浏览器提供的开发人员工具可以发现,虽然这两个cookie的路径(path)都设置为了“/”,但是这两个cookie的域名不一致,虽然我们已经加了Spring Session共享机制,但是后台服务器同样认为这是两个不同的会话(session),可以通过Redis客户端工具(Redis Destop Mananger)查看,先清空,然后访问,发现是维护了两个不同的session,所以不能实现共享,也就是说后台区分是否同一个session和路径和域名有关

    image-20210316230331668

    (1) 解决方案 设置Cook ie的域名为根域名

    @Configuration
    @EnableRedisHttpSession
    public class SessionConfig {
    
        @Bean
        public CookieSerializer cookieSerializer() {
            DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
            cookieSerializer.setCookiePath("/");
            cookieSerializer.setDomainName("dalianpai.com");
            cookieSerializer.setCookieName("dalianpai");
            return cookieSerializer;
        }
    
        @Bean
        public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
            return new GenericJackson2JsonRedisSerializer();
        }
    }
    
    

    image-20210316231122416

  • 相关阅读:
    学期总结
    Sprint 2(第一天)
    学期总结
    实验四、主存空间的分配和回收
    阅读《构建之法》与链接有感.
    《构建之法》八、九、十章读后感
    Scrum领取任务
    实验三、进程调度模拟程序实验
    《构建之法》6-7章读后感
    实验二 作业调度模拟程序
  • 原文地址:https://www.cnblogs.com/dalianpai/p/14583029.html
Copyright © 2020-2023  润新知