文章目录

  • 原理:Redis实现分布式Session
  • web开发session
  • 分布式session同步问题
  • 分布式session解决方案
  • 实战:Redis实现分布式Session
  • 技术栈:Spring Session
  • Spring Session支持功能
  • Spring Session实战
  • 步骤1:依赖包
  • 步骤2:启动类与配置文件
  • 步骤3:实现逻辑
  • 步骤4:编写session拦截器
  • 步骤5:把拦截器注入到拦截器链中
  • 步骤6:测试
  • 测试源码


原理:Redis实现分布式Session

web开发session

在web开发中,我们会把用户的登录信息存储在session里。而session是依赖于cookie的,即服务器创建session时会给它分配一个唯一的ID,并且在响应时创建一个cookie用于存储这个SESSIONID。当客户端收到这个cookie之后,就会自动保存这个SESSIONID,并且在下次访问时自动携带这个SESSIONID,届时服务器就可以通过这个SESSIONID得到与之对应的session,从而识别用户的身。

如下图:

django中如何利用redis实现session分布式管理 基于redis分布式session的实现_spring

分布式session同步问题

现在的互联网应用,基本都是采用分布式部署方式,即将应用程序部署在多台服务器上,并通过nginx做统一的请求分发。而服务器与服务器之间是隔离的,它们的session是不共享的,这就存在session同步的问题了,如下图:

django中如何利用redis实现session分布式管理 基于redis分布式session的实现_服务器_02


如果客户端第一次访问服务器,请求被分发到了服务器A上,则服务器A会为该客户端创建session。如果客户端再次访问服务器,请求被分发到服务器B上,则由于服务器B中没有这个session,所以用户的身份无法得到验证,从而产生了不一致的问题。

分布式session解决方案

解决这个问题的办法有很多,比如可以协调多个服务器,让他们的session保持同步。也可以在分发请求时做绑定处理,即将某一个IP固定分配给同一个服务器。但这些方式都比较麻烦,而且性能上也有一定的消耗。

更合理的方式就是采用类似于Redis这样的高性能缓存服务器,来实现分布式session

从上面的叙述可知,我们使用session保存用户的身份信息,本质上是要做两件事情。第一是保存用户的身份信息,第二是验证用户的身份信息。如果利用其它手段实现这两个目标,那么就可以不用session,或者说我们使用的是广义上的session了。

具体实现的思路如下图,我们在服务端增加两段程序:

django中如何利用redis实现session分布式管理 基于redis分布式session的实现_redis_03

第一是创建令牌的程序,就是在用户初次访问服务器时,给它创建一个唯一的身份标识,并且使用cookie封装这个标识再发送给客户端。那么当客户端下次再访问服务器时,就会自动携带这个身份标识了,这和SESSIONID的道理是一样的,只是改由我们自己来实现了。另外,在返回令牌之前,我们需要将它存储起来,以便于后续的验证。而这个令牌是不能保存在服务器本地的,因为其他服务器无法访问它。因此,我们可以将其存储在服务器之外的一个地方,那么Redis便是一个理想的场所

第二是验证令牌的程序,就是在用户再次访问服务器时,我们获取到了它之前的身份标识,那么我们就要验证一下这个标识是否存在了。验证的过程很简单,我们从Redis中尝试获取一下就可以知道结果

实战:Redis实现分布式Session

技术栈:Spring Session

Spring Session是Spring的项目之一,Spring Session把servlet容器实现的httpSession替换为spring-session,专注于解决session管理问题。

Spring Session提供了集群Session(Clustered Sessions)功能,默认采用外置的Redis来存储Session数据,以此来解决Session共享的问题。

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

Spring Session支持功能

  • 轻易把session存储到第三方存储容器,框架提供了redis、jvm的map、mongo、gemfire、hazelcast、jdbc等多种存储session的容器的方式。这样可以独立于应用服务器的方式提供高质量的集群。
  • 同一个浏览器同一个网站,支持多个session问题。 从而能够很容易地构建更加丰富的终端用户体验。
  • Restful API,不依赖于cookie。可通过header来传递jessionID 。控制session id如何在客户端和服务器之间进行交换,这样的话就能很容易地编写Restful API,因为它可以从HTTP 头信息中获取session id,而不必再依赖于cookie
  • WebSocket和spring-session结合,同步生命周期管理。当用户使用WebSocket发送请求的时候

Spring Session实战

项目目录如下:

django中如何利用redis实现session分布式管理 基于redis分布式session的实现_redis_04

步骤1:依赖包

首先要添加依赖包:因为是web应用。我们加入springboot的常用依赖包web,加入SpringSession、redis的依赖包,移支持把session存储在redis。

这里因为是把seesion存储在redis,这样每个服务登录都是去查看redis中数据进行验证的,所有是分布式的。 这里要引入spring-session-data-redis和spring-boot-starter-redis

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>redis-session-project</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.3.11.RELEASE</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-redis</artifactId>
            <version>1.4.7.RELEASE</version>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

步骤2:启动类与配置文件

编写启动类:

package com.yyl;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RedisSessionApplication {
    public static void main(String[] args) {
        SpringApplication.run(RedisSessionApplication.class, args);
    }
}

添加配置文件:

spring.application.name=spring-boot-redis
server.port=9090
# 设置session的存储方式,采用redis存储
spring.session.store-type=redis
# session有效时长为15分钟
server.servlet.session.timeout=PT15M

## Redis 配置
## Redis数据库索引
spring.redis.database=1
## Redis服务器地址
spring.redis.host=127.0.0.1
## Redis服务器连接端口
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=

步骤3:实现逻辑

创建测试实体类User

模拟初始化用户数据,用map创建,这里主要测试redis就不连数据库了

@Slf4j
@RestController
@RequestMapping(value = "/user")
public class UserController {

    Map<String, User> userMap = new HashMap<>();

    public UserController() {
        //初始化1个用户,用于模拟登录
        User u1=new User(1,"user1","user1");
        userMap.put("user1",u1);
    }
}

登录:登录接口,根据用户名和密码登录,这里进行验证,如果验证登录成功,使用 session.setAttribute(session.getId(), user);把相关信息放到session中。

@GetMapping(value = "/login")
public String login(String username, String password, HttpSession session) {
    //模拟数据库的查找
    User user = this.userMap.get(username);
    if (user != null) {
        if (!password.equals(user.getPassword())) {
            return "用户名或密码错误!!!";
        } else {
            session.setAttribute(session.getId(), user);
            log.info("登录成功{}",user);
        }
    } else {
        return "用户名或密码错误!!!";
    }
    return "登录成功!!!";
}

django中如何利用redis实现session分布式管理 基于redis分布式session的实现_redis_05


查找用户:模拟通过用户名查找用户

/**
 * 通过用户名查找用户
 */
@GetMapping(value = "/find/{username}")
public User find(@PathVariable String username) {
    User user=this.userMap.get(username);
    log.info("通过用户名={},查找出用户{}",username,user);
    return user;
}

获取session

/**
 * 拿当前用户的session
 */
@GetMapping(value = "/session")
public String session(HttpSession session) {
    log.info("当前用户的session={}",session.getId());
    return session.getId();
}

退出登录

/**
 * 退出登录
 */
@GetMapping(value = "/logout")
public String logout(HttpSession session) {
    log.info("退出登录session={}",session.getId());
    session.removeAttribute(session.getId());
    return "成功退出!!";
}

步骤4:编写session拦截器

session拦截器的作用:验证当前用户发来的请求是否有携带sessionid,如果没有携带,提示用户重新登录。

package com.yyl.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;

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

@Slf4j
@Configuration
public class SecurityInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        // 验证当前session是否存在,存在返回true true代表能正常处理业务逻辑
        if (session.getAttribute(session.getId()) != null) {
            log.info("session拦截器,session={},验证通过", session.getId());
            return true;
        }
        // session不存在,返回false,并提示请重新登录。
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write("请登录!!!!!");
        log.info("session拦截器,session={},验证失败", session.getId());

        return false;
    }
}

步骤5:把拦截器注入到拦截器链中

package com.yyl.config;

import com.yyl.interceptor.SecurityInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class SessionCofig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SecurityInterceptor())
                //排除拦截的2个路径
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/logout")
                //拦截所有URL路径
                .addPathPatterns("/**");
    }
}

步骤6:测试

登录user1用户:http://127.0.0.1:9090/user/login?username=user1&password=user1

django中如何利用redis实现session分布式管理 基于redis分布式session的实现_spring_06


django中如何利用redis实现session分布式管理 基于redis分布式session的实现_redis_07

查询user1用户session:http://127.0.0.1:9090/user/session

django中如何利用redis实现session分布式管理 基于redis分布式session的实现_redis_08


django中如何利用redis实现session分布式管理 基于redis分布式session的实现_服务器_09

退出登录: http://127.0.0.1:9090/user/logout

django中如何利用redis实现session分布式管理 基于redis分布式session的实现_spring_10


django中如何利用redis实现session分布式管理 基于redis分布式session的实现_分布式_11

在本地浏览器我们也能找到存的session信息

django中如何利用redis实现session分布式管理 基于redis分布式session的实现_redis_12