前段时间需要搭建一个微服务架构,由于之前一直是搞单体的web应用,对微服务架构这块很短板,在搭建过程中遇到一个很棘手的问题。
项目的需求是做一个电商系统,有后台管理和用户端,因为是多用站发布,所以,后台控制的话要求多站点控制,具体需求就不多说了。
后端框架:nginx,gateway,nacos,sentinel,redist,activiti
前端:vue,jquery,bootstrap
在搭建架构的时候出现了一个问题,就是会话共享,网上看资料微服务有两个方案
1.使用Tokens ,把用户信息用JWT把用户信息加密存在用户的cookie中,或者发送给前端,让前端每次发送都带它
2.使用spring session,和传痛的session相似,只是这个session是被存放在redis中,每个服务器都是从redis取session的。
优缺点我就不说了,网上大把的。
我开始我使用的是tokens的方式,这种方式也没遇到什么问题,就是不太习惯,所以我选择了第二种,第二种,配置完成后,也成功完成了会话之间的共享。
服务注册
测试会话共享
服务器1
服务2
服务3(gateway,因为不是web,所以我在控制台打印了)
之前搞这个会话共享花了也有5-6个小时,因为gateway根本不能使用EnableRedisHttpSession,spring cloud gateway是基于webflux,是非阻塞的,zuul和我们的web服务都是基于servlet的,gateway中需要将EnableRedisHttpSession注解换成EnableRedisWebSession,我替换了好像也没什么用,但是我也没管它,网上有些还说如果用gateway网关是没办法用spring session,都建议用Tokens
我这边在gateway能读取session,是因为我直接去redis读取的。
gateway配置
其它应用服务配置
其它应该用很简单配置好了,在启动类或配置类上加上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
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);
}