分布式session替代方案

396 阅读2分钟

我正在参加「掘金·启航计划」

共享session原理

我们之前单体服务在服务端保存用户登录信息采用的方式和流程如下图:

image-20220929171015412

但是逐渐的我们现在都是采用微服务分布式部署,那么就会出现session要能在不同服务和同服务的集群的共享。这样每次用户请求到不同的集群节点上也能保证正常获取用户的登录信息,在这种情况下我们常见的几种方案有:

  • session复制

image-20220929171028083

常见的tomcat作为服务容器就可以通过配置进行session共享

  • hash一致性

这个的意思就是通过hash将同一个请求都分布在一个服务节点上,相当于固定某一个用户的所有请求只能在一个节点上进行处理,这样在这个节点上就能使用其生成的session信息。

image-20220929171036576

  • 借助三分组件redis

把session放到redis中,这样整个集群都是连接同一个redis,这样都可以在redis中进行获取session信息,可以水平扩展。spring也提供了相应的jar包来支持这种分布式session

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

我们也可以通过扩展来增强

@Configuration
public class SessionConfig {

    @Bean // redis的json序列化
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

    @Bean // cookie
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("MYSESSIONID"); // cookie的键
        serializer.setDomainName("xxx.com"); // 扩大session作用域,也就是cookie的有效域
        return serializer;
    }
}

如果我们不想引入第三方组件那么我们可以换个思路,通过jwt来实现生成token,写入到cookie,之后每次请求都进行解析cookie获取登录信息。大概过程是:

image-20220929171044495

    public static String test(){
        //设置date
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.SECOND,60*1);
        Date date = calendar.getTime();
        //创建token
        String token = JWT.create()
                .withClaim("username","zhangsan")//创建payload
                .withClaim("age",13)//创建payload
                .withExpiresAt(date)//设置超时日期
                .sign(Algorithm.HMAC256("nf!#$%dgr"));//创建签名并设置密钥
        System.out.println(token);
        return token;

    }

    public static void testVerify(){
        //获取token
        String token = test();
        //验证token
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("nf!#$%dgr")).build();
        DecodedJWT decodedJWT = jwtVerifier.verify(token);
        String username = decodedJWT.getClaim("username").asString();
        int age = decodedJWT.getClaim("age").asInt();
        //输出payload的内容
        System.out.println(username);
        System.out.println(age);
    }

我们可以通过定义拦截器进行拦截接口,之后将信息写入当前上下文中,这样就可以在任何一个地方获取存入到上下文中的信息。例如:

import java.util.List;
import java.util.Set;
@Data
public class UserAuthContext<T> {

    private static final ThreadLocal<UserAuthContext> authContextThreadLocal = new ThreadLocal<>();

    // 操作人姓名
    private String userName;
    // 操作人id
    private String id;
    
    public static void remove() {
        authContextThreadLocal.remove();
    }

    public static UserAuthContext get() {
        UserAuthContext authContext = authContextThreadLocal.get();
        if (authContext == null) {
            authContext = new UserAuthContext();
            authContextThreadLocal.set(authContext);
        }
        return authContext;
    }

}

UserAuthContext.setUserName("zhagnsan");

通过这种方式我们就可以在程序中

UserAuthContext.getXxx();