spring session初次体验

最近团队一个项目需要使用Session,之前没有在实际项目中使用过Spring-Session,这里记录一下使用的过程。

Session

Http协议是无状态的,这样对于服务端来说,没有办法区分是新的访客还是旧的访客。但是,有些业务场景,需要追踪用户多个请求,此时就需要Session。关于session的百度百科session

Session:在计算机中,尤其是在网络应用中,称为“会话控制”。Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web页时,如果该用户还没有会话,则Web服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话

核心特点:

  1. 服务端存储
  2. 会过期
Session常用解决方案

对于Session的常用解决方案,可以划分为三种。

  • 负载均衡方式

    借助负载均衡设备或者模块,将指定的Session始终路由到同一台机器即可,如Nginx。

  • 副本复制方式

    利用服务器节点间的副本复制方式,保证集群所有节点拥有的Session数据一致。

  • 集中存储方式

    引入第三方存储,将Session数据集中存储到外部存储中,如Redis或者数据库等。

    本文介绍的Spring-Session是采用第三种,集中存储的方式。

Spring-Session

核心组成模块

  • Spring Session Core

    提供Spring Session核心的功能和API

  • Spring Session Data Redis

    提供基于Redis的SessionRepository以及配置

  • Spring Session JDBC

    提供基于关系型数据库的SessionRepository以及配置

  • Spring Session Hazelcast

    提供基于Hazelcast的SessionRepository以及配置

测试代码

controller提供三个接口,分别对应Session的获取、保存和清理


@GetMapping("/")
public String process(Model model, HttpSession session) {
  @SuppressWarnings("unchecked")
  List<String> messages = (List<String>) session.getAttribute("springSession");

  if (messages == null) {
    messages = new ArrayList<>();
  }
  model.addAttribute("sessionMessages", messages);

  return "sessionTest";
}

@PostMapping("/persistSession")
public String persistMessage(@RequestParam("msg") String msg, HttpServletRequest request) {
  @SuppressWarnings("unchecked")
  List<String> messages = (List<String>) request.getSession().getAttribute("springSession");
  if (messages == null) {
    messages = new ArrayList<>();
    request.getSession().setAttribute("springSession", messages);
  }
  messages.add(msg);
  request.getSession().setAttribute("springSession", messages);
  return "redirect:/";
}

@PostMapping("/destroySession")
public String destroySession(HttpServletRequest request) {
  request.getSession().invalidate();
  return "redirect:/";
}

sessionTest.html对应页面操作

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Spring Boot Session Example</title>
</head>
<body>
<div>
    <form th:action="@{/persistSession}" method="post">
        <textarea name="msg" cols="40" rows="2"></textarea>
        <br> <input type="submit" value="保存" />
    </form>
</div>
<div>
    <h2>session列表</h2>
    <ul th:each="message : ${sessionMessages}">
        <li th:text="${message}">message</li>
    </ul>
</div>

<div>
    <form th:action="@{/destroySession}" method="post">
        <input type="submit" value="清空" />
    </form>
</div>
</body>
</html>

image-20210614151042118

基于数据库的Spring-Session
  1. 引入maven依赖

    使用MySQL存储,所以引入了MySQL。

    涉及到SpringBoot JDBC的配置,引入了Spring Boot JDBC Starter。

    <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-core</artifactId>
      <version>2.5.0</version>
    </dependency>
    
    <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-jdbc</artifactId>
      <version>2.5.0</version>
    </dependency>
    
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.18</version>
    </dependency>
    
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    

    注意:

    No session repository could be auto-configured, check your configuration (session store type is ‘jdbc’)

    如果存在这个报错,是因为没有引入spring-boot-starter-jdbc,引入即可。

  2. 配置application.properties文件

    主要包含两部分,数据库的配置以及Spring Session Jdbc配置。

    # 配置数据源相关内容
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    spring.datasource.url=jdbc:mysql://127.0.0.1:3306/spring_learn?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false
    spring.datasource.username=xxx
    spring.datasource.password=xxx
    spring.datasource.initialization-mode=always
    
    # session类型选择jdbc
    spring.session.store-type=jdbc
    spring.session.jdbc.initialize-schema=always
    # 指定表名
    #spring.session.jdbc.table-name=SESSIONS
    # 超时时间
    spring.session.timeout=180s
    
  3. 数据库存储解析

    默认情况下,数据库中会创建2张表。SPRING_SESSION和SPRING_SESSION_ATTRIBUTION。

    SPRING_SESSION用于存在session自身的一些属性,如创建时间、过期时间等,详细schema如下。

    CREATE TABLE `SPRING_SESSION` (
      `PRIMARY_ID` char(36) NOT NULL,
      `SESSION_ID` char(36) NOT NULL,
      `CREATION_TIME` bigint NOT NULL,
      `LAST_ACCESS_TIME` bigint NOT NULL,
      `MAX_INACTIVE_INTERVAL` int NOT NULL,
      `EXPIRY_TIME` bigint NOT NULL,
      `PRINCIPAL_NAME` varchar(100) DEFAULT NULL,
      PRIMARY KEY (`PRIMARY_ID`),
      UNIQUE KEY `SPRING_SESSION_IX1` (`SESSION_ID`),
      KEY `SPRING_SESSION_IX2` (`EXPIRY_TIME`),
      KEY `SPRING_SESSION_IX3` (`PRINCIPAL_NAME`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC
    

    SPRING_SESSION_ATTRIBUTION用于存储session相关联的属性,schema如下。

    CREATE TABLE `SPRING_SESSION_ATTRIBUTES` (
      `SESSION_PRIMARY_ID` char(36) NOT NULL,
      `ATTRIBUTE_NAME` varchar(200) NOT NULL,
      `ATTRIBUTE_BYTES` blob NOT NULL,
      PRIMARY KEY (`SESSION_PRIMARY_ID`,`ATTRIBUTE_NAME`),
      CONSTRAINT `SPRING_SESSION_ATTRIBUTES_FK` FOREIGN KEY (`SESSION_PRIMARY_ID`) REFERENCES `SPRING_SESSION` (`PRIMARY_ID`) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC
    
  4. 测试执行

    SPRING_SESSION中的数据

    image-20210614151346323

    SPRING_SESSION_ATTRIBUTION中的数据。

    image-20210614151515300

基于Redis的Spring-Session

几乎同样的步骤

  1. maven依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    
  2. application.properties配置

    spring.session.store-type=redis
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    
  3. 结果分析

    一次请求后,多了三个属性,分析如下。

    key类型用途value
    spring:session:sessions:expires:${sessionId}string判断sesssion是否存在
    spring:session:sessions:${sessionId}hashsession相关的属性,包括有效期、创建时间、具体属性等creationTime/lastAccessedTime/sessionAttr/maxInactiveInterval
    spring:session:expirations:1623656160000set存储待过期的sessionId列表key: 过期的时间戳;value: 在这个时间戳将要过期的expire key列表。

    在访问时,先通过第一个key,判断session是否存在以及是否过期。如果没有过期,可以通过第二个key获取或者更新对应的session详情。

    对于第三个key,实际上Spring-Session-Redis会有特殊的用途,主要是为了Redis的keySpace-notificationhttps://redis.io/topics/notifications。核心目的是为了确保过期的session一定要触发过期事件。关于这方面的解释,可以看一下RedisIndexedSessionRepository中的注释。

  4. 订阅Spring-Session的相关事件

    有些时候,我们比较关心Session的创建或者销毁事件,做一些特殊的处理或者记录。基于Redis的Spring-Session利用Spring Event将该事件发布出来,我们可以使用EventListener监听做处理。

    
    @Component
    @Slf4j
    public class AnnotationDrivenEventListener {
    
      @EventListener
      public void handleSessionCreated(SessionCreatedEvent sessionCreatedEvent) {
        String sessionId = sessionCreatedEvent.getSessionId();
        log.info("session id:{} created", sessionId);
      }
    
      @EventListener
      public void handleSessionDestroyed(SessionDestroyedEvent sessionDestroyedEvent) {
        String sessionId = sessionDestroyedEvent.getSessionId();
        log.info("session id:{} destroyed", sessionId);
      }
    }
    
总结

Spring Session提供了非常便利的,基于关系型数据库或者Redis的Session解决方案。

Redis版访问速度快,基于Redis的过期策略,保证过期数据会被删除,同时支持事件订阅。

数据库版直接基于数据库,无需单独引入其他存储。但是访问速度相对较慢,过期数据需要依赖应用程序自身进行删除。同时没有提供事件订阅能力。

参考文章

[1] https://www.javainuse.com/spring/springboot_session

[2] https://www.javainuse.com/spring/springboot_session_redis

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

51iwowo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值