分布式框架之(五)Spring Session—基于Redis的分布式session共享

五、Spring Session—基于Redis的分布式session共享

5.1 背景

我们在开发的过程中,有很多时候常常陷入追求一个功能或者系统的高性能,但却忽略了高可用

我们都知道HttpSession是通过Servlet容器创建和管理的,像Tomcat/Jetty都是保存在内存中的,而如果我们把web服务器搭建成分布式的集群,然后利用LVS或Nginx做负载均衡,那么来自同一用户的Http请求将有可能被分发到两个不同的web站点中去。那么问题就来了,如果不保证系统的高可用行,如何保证不同的web站点能够共享同一份session数据呢?

当同一个用户通过浏览器去访问时:
在这里插入图片描述

这时如果TomcatA修改session中的数据,TomcatB中的session并不会改变,这就会出现session不一致,那么这个问题该怎么解决呢?

5.2 session不一致问题解决方案

  • Session 黏连

    使用 nginx 实现会话黏连,将相同 sessionid 的浏览器所发起的请求,转发到同一台服务器。这样,就不会存在多个 Web 服务器创建多个 Session 的情况,也就不会发生 Session 不一致的问题。

    也就是说通过nginx的负载均衡做 ip_hash ,路由到特定的服务器上(通过客户端请求ip进行hash,再通过hash值选择后端server)来实现。

    不过,这种方式常会出现单点故障,目前基本不被采用。因为,如果一台服务器重启,那么会导致转发到这个服务器上的 Session 全部丢失。

    注:ip_hash是通过IP地址做循环,循环时只是将IP的前三个端作为参数加入hash函数。这样做的目的是保证ip地址前三位相同的用户经过hash计算将分配到相同的后端server。

  • Session 复制

    Web 服务器之间,进行 Session 复制同步。仅仅适用于实现 Session 复制的 Web 容器,例如说 Tomcat 、Weblogic 等等。

    不过,这种方式目前基本也不被采用。试想一下,如果我们有 5 台 Web 服务器,所有的 Session 都要同步到每一个节点上,一个是效率低,一个是浪费内存。

    具体怎么实现这种方式,可以自己去查阅一下有关资料哈。

  • Session 外部化存储

    不同于上述的两种方案,Session 外部化存储,考虑不再采用 Web 容器的内存中存储 Session ,而是将 Session 存储外部化,持久化到 MySQL、Redis、MongoDB 等等数据库中。这样,Tomcat 就可以无状态化,专注提供 Web 服务或者 API 接口,未来拓展扩容也变得更加容易。

    这种方式就是我们的“主角”!

5.3 Spring Session概述

查看官方文档:https://spring.io/projects/spring-session

Spring Session是Spring的项目之一,把servlet容器实现的httpSession替换为spring-session,专注于解决session管理问题。Spring Session提供了集群Session(Clustered Sessions)功能,默认采用外置的Redis来存储Session数据,以此来解决Session共享的问题。

spring-session提供对用户session管理的一系列api和实现。提供了很多可扩展、透明的封装方式用于管理httpSession/WebSocket的处理。

5.4 Spring Session+Redis快速入门

创建一个springboot项目模块,不会建的童鞋可以参考一下:https://blog.csdn.net/qq_50994235/article/details/119330209,我这里还是基于:通过Dubbo来实现服务消费方远程调用服务提供方的方法创建的两个模块springboot_dubbo_provider和springboot_dubbo_consumer来做说明:
在这里插入图片描述
废话不多说,直接上代码!

配置代码
  • 服务提供方

    1. 引入相关依赖

      <?xml version="1.0" encoding="UTF-8"?>
      <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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <parent>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-parent</artifactId>
              <version>2.5.3</version>
              <relativePath/> <!-- lookup parent from repository -->
          </parent>
          <groupId>cn.ebuy</groupId>
          <artifactId>springboot_dubbo_provider</artifactId>
          <version>0.0.1-SNAPSHOT</version>
          <name>springboot_dubbo_provider</name>
          <description>Demo project for Spring Boot</description>
          <properties>
              <java.version>9</java.version>
          </properties>
          <dependencies>
              <!--是一个web场景启动器,启动的是springboot的web场景 —— springweb核心组件-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
              <!--启动web场景需要的依赖-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter</artifactId>
              </dependency>
              <!--测试场景的启动器-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
                  <scope>test</scope>
              </dependency>
              <!--dubbo —— springboot依赖-->
              <dependency>
                  <groupId>org.apache.dubbo</groupId>
                  <artifactId>dubbo-spring-boot-starter</artifactId>
                  <version>2.7.6</version>
              </dependency>
              <!--注册中心zookeeper-->
              <dependency>
                  <groupId>org.apache.dubbo</groupId>
                  <artifactId>dubbo-registry-zookeeper</artifactId>
                  <version>2.7.6</version>
                  <exclusions>
                      <exclusion>
                          <groupId>org.apache.curator</groupId>
                          <artifactId>curator-recipes</artifactId>
                      </exclusion>
                  </exclusions>
              </dependency>
              <!--选举集群中的leader-->
              <dependency>
                  <groupId>org.apache.curator</groupId>
                  <artifactId>curator-recipes</artifactId>
                  <version>4.2.0</version>
              </dependency>
          </dependencies>
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                  </plugin>
              </plugins>
          </build>
      </project>
      
    2. 应用配置文件

      server:
        port: 8081  #模拟配置8081和8082两个服务提供方
      dubbo:
        application:
          name: springboot_dubbo_provider
        registry:
          address: 127.0.0.1:2181 #注册中心地址
          protocol: zookeeper
        protocol:
          port: 20881 #注册端口号   注册20881和20882两个端口,与8081和8082对应
          name: dubbo #注册协议
        #只要Spring Boot启动文件和要扫描的文件在同一级目录下,就不用配置扫描
        scan:
          base-packages: cn.ebuy.service.impl
      
    3. 接口及实现类(只是为了测试是否遵循负载均衡策略)

      //接口
      package cn.ebuy.service;
      public interface HelloService {
          public String sayHello(String name);
      }
      //实现类
      package cn.ebuy.service.impl;
      import cn.ebuy.service.HelloService;
      import org.apache.dubbo.config.annotation.Service;
      @Service(loadbalance = "roundrobin")
      public class HelloServiceImpl implements HelloService{
          @Override
          public String sayHello(String name)
          {
              return "hello---8082---20882----"+name;
          }
      }
      
      
    4. 启动类

      package cn.ebuy;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      @SpringBootApplication
      public class SpringbootDubboProviderApplication {
          public static void main(String[] args) {
              SpringApplication.run(SpringbootDubboProviderApplication.class, args);
          }
      }
      
  • 服务消费方

    1. 引入依赖

      <?xml version="1.0" encoding="UTF-8"?>
      <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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
          <modelVersion>4.0.0</modelVersion>
          <!--这是Spring Boot的父级依赖,这样当前的项目就是Spring Boot项目了。
          spring-boot-starter-parent是一个特殊的starter,它用来提供相关的Maven默认依赖。
          使用它之后,常用的包依赖可以省去version标签-->
          <parent>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-parent</artifactId>
              <version>2.5.3</version>
              <relativePath/> <!-- lookup parent from repository -->
          </parent>
          <groupId>cn.ebuy</groupId>
          <artifactId>springboot_dubbo_consumer</artifactId>
          <version>0.0.1-SNAPSHOT</version>
          <name>springboot_dubbo_consumer</name>
          <description>Demo project for Spring Boot</description>
          <properties>
              <java.version>9</java.version>
          </properties>
          <dependencies>
              <!--是一个web场景启动器,启动的是springboot的web场景 —— springweb核心组件-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-web</artifactId>
              </dependency>
              <!--启动web场景需要的依赖-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter</artifactId>
              </dependency>
              <!--测试场景的启动器-->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-test</artifactId>
                  <scope>test</scope>
              </dependency>
              <!--dubbo —— springboot依赖-->
              <dependency>
                  <groupId>org.apache.dubbo</groupId>
                  <artifactId>dubbo-spring-boot-starter</artifactId>
                  <version>2.7.6</version>
              </dependency>
              <!--注册中心zookeeper-->
              <dependency>
                  <groupId>org.apache.dubbo</groupId>
                  <artifactId>dubbo-registry-zookeeper</artifactId>
                  <version>2.7.6</version>
                  <exclusions>
                      <exclusion>
                          <groupId>org.apache.curator</groupId>
                          <artifactId>curator-recipes</artifactId>
                      </exclusion>
                  </exclusions>
              </dependency>
              <!--选举集群中的leader-->
              <dependency>
                  <groupId>org.apache.curator</groupId>
                  <artifactId>curator-recipes</artifactId>
                  <version>4.2.0</version>
              </dependency>
              <!-- 实现对Spring Session使用Redis作为数据源的自动化配置 -->
              <dependency>
                  <groupId>org.springframework.session</groupId>
                  <artifactId>spring-session-data-redis</artifactId>
              </dependency>
              <!-- 实现对Spring Data Redis的自动化配置 -->
              <dependency>
                  <groupId>org.springframework.boot</groupId>
                  <artifactId>spring-boot-starter-data-redis</artifactId>
                  <exclusions>
                      <!-- 去掉对 Lettuce 的依赖,因为Spring Boot优先使用Lettuce作为Redis客户端 -->
                      <exclusion>
                          <groupId>io.lettuce</groupId>
                          <artifactId>lettuce-core</artifactId>
                      </exclusion>
                  </exclusions>
              </dependency>
              <!-- 引入Jedis的依赖,这样 Spring Boot实现对Jedis的自动化配置 -->
              <dependency>
                  <groupId>redis.clients</groupId>
                  <artifactId>jedis</artifactId>
              </dependency>
          </dependencies>
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                  </plugin>
              </plugins>
          </build>
      </project>
      
    2. 应用配置文件

      server:
        port: 2011  #模拟配置2011 2012 2013三个服务消费方
      dubbo:
        application:
          name: springboot_dubbo_consumer
        registry:
          address: 127.0.0.1:2181 #注册中心地址  一个注册中心
          protocol: zookeeper
          check: false
      spring:
        #配置spring session保存在redis中
        redis:
          password: 123456 #redis服务器密码,默认为空,自己设置。
          jedis:
            pool:
              max-active: 8 #连接池最大连接数,默认为0,使用负数表示没有限制。
              max-idle: 8 #默认连接数最大空闲的连接数,默认为8,使用负数表示没有限制。
              min-idle: 0 #默认连接池最小空闲的连接数,默认为0,允许设置0和正数。
              max-wait: -1 #连接池最大阻塞等待时间,单位:毫秒。默认为-1,表示不限制。
          host: 127.0.0.1
          port: 6379
      
    3. SessionConfiguration

      package cn.ebuy.util;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.data.redis.serializer.RedisSerializer;
      import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
      /**
       * 自定义RedisSerializer并返回成json格式的数据,便于查看session中的数据
       */
      @Configuration
      //自动化配置Spring Session使用Redis作为数据源
      @EnableRedisHttpSession
      public class SessionConfiguration {
          //创建 {@link RedisOperationsSessionRepository}使用的RedisSerializer Bean 。
          @Bean(name = "springSessionDefaultRedisSerializer")
          /**
           * 注意:RedisSerializer的name要命名为"springSessionDefaultRedisSerializer",
           * 否则这个redis序列器是不生效的。
           *
           * 在@EnableRedisHttpSession这个注解中有@Import({RedisHttpSessionConfiguration.class})
           * 导入了RedisHttpSessionConfiguration配置,而这个类RedisHttpSessionConfiguration中标注了
           * @Qualifier("springSessionDefaultRedisSerializer"),所以必须命名"springSessionDefaultRedisSerializer",
           * 否则这个redis序列器是不生效的。
           */
          public RedisSerializer springSessionDefaultRedisSerializer(){
              //采用JSON序列化方式。因为默认情况下,采用 Java自带的序列化方式 ,可读性很差,所以进行替换。
              return RedisSerializer.json();
          }
      }
      
    4. 接口直接复制即可

    5. controller

      package cn.ebuy.controller;
      import cn.ebuy.service.HelloService;
      import org.apache.dubbo.config.annotation.Reference;
      import org.springframework.stereotype.Controller;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.ResponseBody;
      import javax.servlet.http.HttpServletRequest;
      @Controller
      @RequestMapping("/demo")
      public class HelloController {
          @Reference
          HelloService helloService;
          int port=2011; //模拟三个端口号
          @RequestMapping("/toIndex")
          @ResponseBody
          public String doLogin(HttpServletRequest request){
              System.out.println(port);
              request.getSession().setAttribute("USERS","zhangsan");
              return "success";
          }
          @RequestMapping("/doIndex")
          @ResponseBody
          public String doIndex(HttpServletRequest request){
              System.out.println(port+"=="+request.getSession().getAttribute("USERS"));
              return (String)request.getSession().getAttribute("USERS");
          }
      }
      
    6. 启动类

      package cn.ebuy;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      @SpringBootApplication
      public class SpringbootDubboConsumerApplication {
          public static void main(String[] args) {
              SpringApplication.run(SpringbootDubboConsumerApplication.class, args);
          }
      }
      
运行测试
  • 启动redis
    在这里插入图片描述

  • 启动zookeeper并发布服务
    在这里插入图片描述

  • 在页面上输入地址:http://www.ebuy.cn/demo/toIndex进行访问测试
    在这里插入图片描述

  • 在页面上输入地址:http://www.ebuy.cn/demo/doIndex进行访问测试
    在这里插入图片描述

这不就欧了嘛…
请添加图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值