Spring Session 实现分布式环境下session共享问题



http请求是无状态的问题

http请求是无状态的,用户免登录等问题需要记录用户登录状态(即需要请求有状态),为了解决这个问题引入了 浏览器端的cookie 和 服务器端的session

这里要引入一个概念sessionid,session对象当客户端首次访问时,创建一个新的session对象.并同时生成一个sessionId,并在此次响应中将sessionId以响应报文的方式些回客户端浏览器内存或以重写url方式送回客户端,来保持整个会话

也就是说客户端request请求时候,如果获取了session,就默认分配一个sessionid,然后通过response响应到客户端cookie,然后客户端下一次请求,默认会携带这个sessionid请求到服务端,服务端拿到这个jessionid来区分不同的客户端。



session 原理

首次请求
在这里插入图片描述
session未失效情况
在这里插入图片描述
session失效情况
在这里插入图片描述



分布式环境下,session丢失问题(无法共享问题)

session会话在单台服务器下不会出现共享问题,现在应用部署方式都是分布式,或者集群部署,这样必然会面临一个问题,session无法共享
在这里插入图片描述
解决方案:使用公共的session对象存储位置(redis)
在这里插入图片描述

分布式环境下,session共享解决方案

1 nginx方案

nginx提供了ip_hash策略,可以保持用户ip进行hash值计算固定分配到某台服务器上,然后只要是该ip则会保持分配到该服务器上,保证用户访问的是同一台服务器,那么session问题就不存在了。这也是解决session共享的一种方式,也称为黏性session。但是假设一台tomcat服务器挂了的话,那么session也会丢失。所以比较好的方案是抽取session。
在这里插入图片描述

2. tomcat方案

tomcat与redis集成实现session共享:
环境为tomcat7 + jdk1.6的话:
在所有需要共享session的服务器的tomcat中目录下:
lib目录中添加以下三个jar包,注意版本最好一致,不然极容易出现错误,下边的测试是可用的:
在这里插入图片描述
conf目录中content.xml中加入:配置redis服务
在这里插入图片描述

环境为tomcat7 + jdk1.7或1.8的话:
在所有需要共享session的服务器的tomcat中目录下:
lib目录中添加以下三个jar包,测试通过:
在这里插入图片描述
conf目录中content.xml中加入:配置redis服务
在这里插入图片描述
根据我这测试,是jkd1.8+tomcat7,在137和139两台tomcat中加入jar包且进行如上配置:
在这里插入图片描述
修改content.xml
在这里插入图片描述

两个注意点
1、按照如上配置,使用redis数据库,放入session中的对象必须要实现java.io.Serializable接口,使用memcache的可以不用实现Serializable接口
原因是:因为tomcat里使用的将session放置redis使用的工具类,是使用的jdk序列化模式存储的,这一点也是很容易理解的,session.setAttribute(String key, Object value),存储Object类型object放入redis中又要能取出来,只能是序列化进行存储了,然后取出的时候进行反序列化。
所以我们在session中存储的任何对象,都必须实现序列化接口。

2、按照如上配置,使用redis做session存储空间时,web应用的session-time的时间单位会变成[秒],而不是原本的[分]
原因是:因为tomcat里使用的将session放置redis使用的工具类,在存储时为对tomcat容器时间做转换在redis中设置过期时间是使用秒作为单位的,有个命令叫expire可以设置redis键值过期时间,所以在context.xml配置文件中我们需要制定session过期时间(默认是60秒,配成1800即30分钟),这一点很重要。
请注意!!!!
context.xml配置说明:
在这里插入图片描述



3 Spring Session + Redis (推荐)

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

创建工程结构如下:
在这里插入图片描述
在两个子工程session_service1与session_service2中分别添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

分别修改配置文件application.yml:

spring:
  # 配置redis
  redis:
    host: 192.168.0.150
    port: 6379
    database: 2 # 使用的数据库
    # 配置Jedis连接池属性
    jedis:
      pool:
        min-idle: 5 # 连接池中的最小空闲连接
        max-idle: 10 # 连接池中的最大空闲连接
        max-active: 10 #连接池最大连接数,负值没有限制
        max-wait: 2000 # 连接池最大阻塞等待时间(使用负值表示没有限制)

  # spring session配置
  session:
    # 设置 Spring Session 使用 Redis 进行存储。默认配置就是 redis
    store-type: redis

    # 设置 Spring Session 的过期时间。如果不指定单位模式是 s。
    # 也可以通过在启动类上声明@EnableRedisHttpSession进行配置。
    # 例如:@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
    timeout: 1m # 1分钟

    # 缓存到redis相关配置
    redis:
      # session刷新模式:
      #  (1)on_save: 保存时刷新,即响应结束后刷新。默认是 on_save
      #  (2)immediate 实时刷新。
      # 也可以通过在启动类上声明@EnableRedisHttpSession进行配置。
      # 例如:@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.ON_SAVE)
      flush-mode: on_save

      #  redis存储 Session的命名空间(命名空间主要是用于多个服务之间进行区分)。默认是spring:session
      #  也可以通过在启动类上声明@EnableRedisHttpSession进行配置。
      #  例如:@EnableRedisHttpSession(redisNamespace=“xxxx”)
      namespace: spring:session

session_service1中添加创建session的请求接口

package com.haoqian.session_service1.controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("/session")
public class SpringSessionTestController {
    @RequestMapping("/add/{name}/{value}")
    public String addSession(HttpServletRequest request,
                             @PathVariable("name") String name,
                             @PathVariable("value") String value) {
        HttpSession session = request.getSession();
        session.setAttribute(name, value);
        return "sessionId:" + session.getId() + " name:" + name;
    }
}

session_service1中添加过去session的请求接口

package com.haoqian.session_service2.controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("/session")
public class SpringSessionTestController {

    @RequestMapping("/get/{name}")
    public String getSession(HttpServletRequest request,
                             @PathVariable("name") String name) {
        HttpSession session = request.getSession();
        String value = (String) session.getAttribute(name);
        return "sessionId:" + session.getId() + " value:" + value;
    }
}

大功告成!整合完毕,是不是很简单呢

测试:

(0)准备

测试前redis中没有数据
在这里插入图片描述
浏览器的cookie中也没有存储sessionId
在这里插入图片描述

(1)请求session_service1的添加session接口
在这里插入图片描述
从浏览器控制台上可以看到 响应头中返回了加密的 sessionId
在这里插入图片描述

(2)请求session_service2的获取session接口
在这里插入图片描述
从浏览器控制台上可以看到 请求头中携带了加密的 sessionId
在这里插入图片描述

(3)session的存储

在redis中存储了session对象(值得注意的是,redis中缓存的session失效一定时间后会自动移除,不会一致占用内存)
在这里插入图片描述
在浏览器cookie中缓存了加密后的sessionId
在这里插入图片描述

相同父域名,不同子域名的Session共享

配置本地域名映射 C:\Windows\System32\drivers\etc\hosts 文件

127.0.0.1       sh.session.com
127.0.0.1       bj.session.com

使用域名访问之前上边的两个接口发现 无法获取session
在这里插入图片描述
在这里插入图片描述
这是因为浏览器的安全机制,使得session在浏览器端默认不能跨域携带

修改两个spring boot工程的配置文件添加如下配置即可解决问题
但是这样配置后,只有父域名为session.com的才会存储使用相同session

server:
  servlet:
    session:
      cookie:
        # 指定Cookie的存放路径为根路径(实现同域名下不同项目的session共享)
        # path: /
        # 指定Cookie的存放根域名(实现相同根域名,不同子域名的Session共享)
        domain: session.com
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值