微服务gateway 获取共享session

前段时间需要搭建一个微服务架构,由于之前一直是搞单体的web应用,对微服务架构这块很短板,在搭建过程中遇到一个很棘手的问题。

项目的需求是做一个电商系统,有后台管理和用户端,因为是多用站发布,所以,后台控制的话要求多站点控制,具体需求就不多说了。

后端框架:nginx,gateway,nacos,sentinel,redist,activiti

前端:vue,jquery,bootstrap

在搭建架构的时候出现了一个问题,就是会话共享,网上看资料微服务有两个方案

1.使用Tokens ,把用户信息用JWT把用户信息加密存在用户的cookie中,或者发送给前端,让前端每次发送都带它

2.使用spring session,和传痛的session相似,只是这个session是被存放在redis中,每个服务器都是从redis取session的。

优缺点我就不说了,网上大把的。

我开始我使用的是tokens的方式,这种方式也没遇到什么问题,就是不太习惯,所以我选择了第二种,第二种,配置完成后,也成功完成了会话之间的共享。

服务注册

微服务gateway 获取共享session_第1张图片

 测试会话共享

服务器1

微服务gateway 获取共享session_第2张图片

 服务2

微服务gateway 获取共享session_第3张图片

服务3(gateway,因为不是web,所以我在控制台打印了)

微服务gateway 获取共享session_第4张图片

 之前搞这个会话共享花了也有5-6个小时,因为gateway根本不能使用EnableRedisHttpSession,spring cloud gateway是基于webflux,是非阻塞的,zuul和我们的web服务都是基于servlet的,gateway中需要将EnableRedisHttpSession注解换成EnableRedisWebSession,我替换了好像也没什么用,但是我也没管它,网上有些还说如果用gateway网关是没办法用spring session,都建议用Tokens

我这边在gateway能读取session,是因为我直接去redis读取的。

gateway配置

微服务gateway 获取共享session_第5张图片

其它应用服务配置

 微服务gateway 获取共享session_第6张图片

其它应该用很简单配置好了,在启动类或配置类上加上EnableRedisHttpSession注解就可以了

 gateway不用加因为你只需要在redis读取session就可以了,毕竟gateway不做主要业务处理

但是要获取session还需要创建一个配置类

package com.example.gateway.config;

import io.lettuce.core.ClientOptions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;

//@EnableRedisWebSession
//@EnableRedisHttpSession
@Configuration
@Slf4j
public class RedisSessionConfig {
    @Value("${spring.redis.database}")
    int database;
    @Value("${spring.redis.host}")
    String host;
    @Value("${spring.redis.port}")
    int port;
    @Value("${spring.redis.password}")
    String password;
    @Bean
    public LettuceConnectionFactory lettuceConnectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(host, port);
        configuration.setPassword(RedisPassword.of(password));
        configuration.setDatabase(database);
        LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
                .clientOptions(ClientOptions.builder().build())
                .build();
        return new LettuceConnectionFactory(configuration,clientConfiguration);
    }

    @Bean
    public RedisSerializer springSessionDefaultRedisSerializer() {
        // 配置Redis序列化器
        return new GenericJackson2JsonRedisSerializer();
    }
    //应为映入的jar包已经有这个sessionRepository实例注入了,所以这里我随便写了一个,
    @Bean(name="sessionRepository1")
    public RedisIndexedSessionRepository sessionRepository(RedisSerializer springSessionDefaultRedisSerializer) {
        // 配置RedisSessionRepository

        RedisIndexedSessionRepository sessionRepository = new RedisIndexedSessionRepository(redisOperations());
        //sessionRepository.setRedisKeyNamespace("constellation:session:id");
        sessionRepository.setDefaultSerializer(springSessionDefaultRedisSerializer);
        return sessionRepository;
    }
    @Bean
    public RedisOperations redisOperations() {
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(lettuceConnectionFactory());
        redisTemplate.setKeySerializer(springSessionDefaultRedisKeySerializer());
        redisTemplate.setValueSerializer(springSessionDefaultRedisSerializer());
        redisTemplate.setHashKeySerializer(springSessionDefaultRedisKeySerializer());
        redisTemplate.setHashValueSerializer(springSessionDefaultRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    @Bean
    public RedisSerializer springSessionDefaultRedisKeySerializer() {
        return new StringRedisSerializer();
    }

}

gateway测试获取应用服务的session

package com.example.gateway.filter;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.session.Session;
import org.springframework.session.data.redis.RedisIndexedSessionRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.Base64;

@Component
public class MainGlobalFilter implements GlobalFilter, Ordered {
    @Resource(name="sessionRepository1")
    RedisIndexedSessionRepository sessionRepository;

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取 Session
        String cookieValue = exchange.getRequest().getCookies().getFirst("SESSION").getValue();
        System.out.println(cookieValue);
        String sessionId = new String(Base64.getDecoder().decode(cookieValue));
        System.out.println(sessionId);
       Session byId = sessionRepository.findById(sessionId);
        System.out.println(byId);
        if(null!=byId){
            System.out.println(byId.getAttribute("test")+"");
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

这样就可以得到session了,得到session,当然就可以得到session的换成数据了。

因为这个RedisIndexedSessionRepository.RedisSession被保护起来了,所以我只能用接口类Session来接受,RedisIndexedSessionRepository.RedisSession被保护应该是不想我们随意的去操作数据吧。反正我的gateway只是做数据验证,不做业务数据持久所以无所谓。

值得注意的是,这种方式只能读取数据没办法写入数据,可以根据直接需求决定是否这么用。

如果你非要往session里面写数据的话,只能用点手段了,也不是不可以。使用放射强行写进去就可以了。

 byId.setAttribute("test1","gateway");
            try {
                Class redisSessionClass = Class.forName("org.springframework.session.data.redis.RedisIndexedSessionRepository$RedisSession");
                Method method = redisSessionClass.getDeclaredMethod("save");
                method.setAccessible(true);
                Object result = method.invoke(byId);
                System.out.println(result);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }

你可能感兴趣的:(酷爱Java,微服务,微服务,前端,架构,gateway)