Redis实现Session共享

1.使用Redis实现Session共享

在微服务架构中,往往由多个微服务共同支撑前端请求,如果涉及到用户状态就需要考虑分布式Session管理问题,比如用户登录请求分发在服务器A,用户购买请求分发在服务器B,那么服务器就必须可以获取到用户的登录信息,否则就会影响正常交易,因此,在分布式架构或微服务架构下,必须保证一个应用服务器上保存Session后,其他应用服务器可以同步或共享这个Session

2.目前主流的分布式Session管理由两种方案
  • Session复制

    部分Web服务器能够支持Session复制功能,如Tomcat。用户通过修改Web服务器的配置文件,让Web服务器进行Session存储,保持每一个Web服务器节点的Session数据都能达到一致

    这种方案的实现依赖于Web服务器,需要Web服务器有Session复制功能。当Web应用中Session数量较多的时候,每个服务器节点都需要有一部分内存来存放Session,将占用大量内存资源。同时大量的·Session对象通过网络传输进行复制,不但占用来网络资源,还会因为复制同步出现延迟,导致程序运行错误

    在微服务架构中,往往需要N个服务端来共同支撑服务,不建议采用这种方案

  • Session集中存储

    在单独的服务器或带我去集群上使用缓存技术,如Redis存储Session数据,集中管理所有的Session,所有的Web服务器都从这个存储介质中获取对应的Session,是继续Session共享,将Session信息从应用中剥离出来后,其实就达到了服务的无状态化,这样就方便在业务极速发展时水平扩展

    在微服务架构下,推荐采用此方案

3.Session共享

1.什么是Session

由于HTTP协议是无状态的协议,因而服务器端需要记录用户状态时,就需要用某种机制来识别具体的用户。Session时另一种积累客户记录的机制,不同的是Cookie保持在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户信息以某种形式记录在浏览器上,这就是Session。客户端浏览器再次访问时只需要从该Session中查找该用户状态就可以了

2.为什么需要Session共享

在互联网行业中用户量访问巨大,往往需要多个节点共同对外提供某一种服务,如下图:

用户的请求首先会到达前置网关,前置网关根据路由策略将请求分发到后端的服务器,这就会出现第一次的请求会交给服务器A处理,下次请求可能会时服务器B处理,如果不做Session共享的话,就可能出现用户在服务A登录了,下次请求到达服务器B又要去用户重新登录

前置网关我们一般使用 lvs、Nginx 或者 F5 等软硬件,有些软件可以指定策略让用户每次请求都分发到同一台服务器中,这也有个弊端,如果当其中一台服务 Down 掉之后,就会出现一批用户交易失效。在实际工作中我们建议使用外部的缓存设备来共享 Session,避免单个节点挂掉而影响服务,使用外部缓存 Session 后,我们的共享数据都会放到外部缓存容器中,服务本身就会变成无状态的服务,可以随意的根据流量的大小增加或者减少负载的设备。

Spring 官方针对 Session 管理这个问题,提供了专门的组件 Spring Session,使用 Spring Session 在项目中集成分布式 Session 非常方便。

3.Spring Session

Spring Session 提供了一套创建和管理 Servlet HttpSession 的方案。Spring Session 提供了集群 Session(Clustered Sessions)功能,默认采用外置的 Redis 来存储 Session 数据,以此来解决 Session 共享的问题。

Spring Session 为企业级 Java 应用的 Session 管理带来了革新,使得以下的功能更加容易实现:

  • API 和用于管理用户会话的实现;
  • HttpSession,允许以应用程序容器(即 Tomcat)中性的方式替换 HttpSession;
  • 将 Session 所保存的状态卸载到特定的外部 Session 存储中,如 Redis 或 Apache Geode 中,它们能够以独立于应用服务器的方式提供高质量的集群;
  • 支持每个浏览器上使用多个 Session,从而能够很容易地构建更加丰富的终端用户体验;
  • 控制 Session ID 如何在客户端和服务器之间进行交换,这样的话就能很容易地编写 Restful API,因为它可以从 HTTP 头信息中获取 Session ID,而不必再依赖于 cookie;
  • 当用户使用 WebSocket 发送请求的时候,能够保持 HttpSession 处于活跃状态。

需要说明的很重要的一点就是,Spring Session 的核心项目并不依赖于 Spring 框架,因此,我们甚至能够将其应用于不使用 Spring 框架的项目中。

4.快速集成

引入依赖包

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

添加配置文件

# 数据库配置

spring.datasource.driverClassName=com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/day1122?useUnicode=true&characterEncoding=utf8

spring.datasource.username=root

spring.datasource.password=root
# 显示运行SQL
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

#module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
## 自定义日志打印
#logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
##日志输出到控制台
#appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
## 使用日志系统记录 sql
##appender=com.p6spy.engine.spy.appender.Slf4JLogger
## 设置 p6spy driver 代理
#deregisterdrivers=true

# 设置编码类型
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.messages.encoding=UTF-8

# Redis 数据库索引(默认为 0)
spring.redis.database=0
# Redis 服务器地址
spring.redis.host=localhost
# Redis 服务器连接端口
spring.redis.port=6379
# Redis 服务器连接密码(默认为空)
spring.redis.password=123
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0

在项目目录下创建一个config包,在包中创建SessionConfig.java在该内中编写配置

package com.jn.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
 * @Author ScholarTang
 * @Date 2020/2/14 11:08 AM
 * @Desc Session配置类
 */

@Configuration
//maxInactiveIntervalInSeconds: 设置 Session 失效时间,使用 Redis Session 之后,
//原 Spring Boot 中的 server.session.timeout 属性不再生效。
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 86400 * 30)
public class SessionConfig {
}

完成以上两步SpringBoot分布式Session就配置好了

验证Session共享

1.在项目目录下创建一个controller包,在包中创建SessionController.java类在类中编写两个接口,一个生成session,一个获取token。

2.将项目copy一份,此时就有了两个访问

3.验证:

验证方式访问服务A生成session的接口和获取session的接口,服务B访问获取session的接口。对比两个不同分为获取到的sessionId是否一样,如果一样则实现了session共享

package com.jn.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author ScholarTang
 * @Date 2020/2/14 11:21 AM
 * @Desc 获取 / 存储Session
 */

@Slf4j
@RestController
public class SessionController {


    /**
     * TODO 设置 Session
     * @param request
     * @return
     */
    @RequestMapping("/setSession")
    public Map<String, Object> setSession(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        request.getSession().setAttribute("sessionId",request.getRequestURL());
        map.put("request Url", request.getRequestURL());
        return map;
    }


    /**
     * TODO 获取 Session
     * @param request
     * @return
     */
    @RequestMapping("/getSession")
    public Map<String, Object> getSession(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        map.put("sessionId", request.getSession().getId());
        map.put("message",request.getSession().getAttribute("message"));
        return map;
    }
}

模拟登录操作

用户登出和访问首页

用户登录了(执行请求的可能是A服务,也有可能是B服务),假定登录请求是服务A处理了,登录成功将session存储到redis中间间中,然后首页的请求是服务B处理的,服务B处理首页的方法获取redis中间间中的session,如果获取到的session不为口则获取首页相应的数据,否则返回对应的提示信息

package com.jn.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.jn.domain.Admin;
import com.jn.service.AdminService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;

/**
 * @Author ScholarTang
 * @Date 2020/2/14 11:44 AM
 * @Desc 处理用户相关的业务
 */

@Slf4j
@RestController
public class AdminController {

    @Autowired
    private AdminService service;


    /**
     * TODO 登录
     * @param
     * @param request
     * @return
     */
    @PostMapping("/login")
    public String login (@RequestBody Admin admin,HttpServletRequest request) {
        String msg = "登录失败!";
        Admin dbAdmin = service.getOne(new LambdaQueryWrapper<Admin>().eq(Admin::getUsername,admin.getUsername()));
        if (dbAdmin != null) {
            if (admin.getPassword().equals(dbAdmin.getPassword())) {
                request.getSession().setAttribute("user",dbAdmin);
                Object user = request.getSession().getAttribute("user");
                if (user != null) {
                    System.out.println("session is ok");
                } else {
                    System.out.println("session is no");
                }
                msg = "登录成功!";
            }
        }
        return msg;
    }

    /**
     * TODO 退出
     * @param request
     * @return
     */
    @RequestMapping("/loginOut")
    public String loginOut(HttpServletRequest request) {
        Object user = request.getSession().getAttribute("user");
        if (user != null) {
            System.out.println("session is ok");
        } else {
            System.out.println("session is no");
        }
        request.getSession().removeAttribute("user");
        return "退出成功!";
    }

    @RequestMapping("/index")
    public String index(HttpServletRequest request) {
        String msg = "亲!请先登录";
        Object user = request.getSession().getAttribute("user");
        if (user != null) {
            msg = "这里是首页!";
        }
        return msg;
    }
}
使用 Redis 作为 Session 共享之后的示意图:

从上图可以看出,所有的服务都将 Session 的信息存储到 Redis 集群中,无论是对 Session 的注销、更新都会同步到集群中,达到了 Session 共享的目的。

总结

在微服务架构下,系统被分割成大量的小而相互关联的微服务,因此需要考虑分布式 Session 管理,方便平台架构升级时水平扩充。通过向架构中引入高性能的缓存服务器,将整个微服务架构下的 Session 进行统一管理。

Spring Session 是 Spring 官方提供的 Session 管理组件,集成到 Spring Boot 项目中轻松解决分布式 Session 管理的问题。

5.码云地址

https://gitee.com/Tang1314521/SpringSession.git

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值