一、Session 共享

在分布式微服务中,经常会部署集群服务,如果我们在8001服务登陆了,如果使用SpringSessing在8002服务、8003服务的时候就不需要再次登陆啦。

导入依赖

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

yml

配置springsession和redis配置

spring:
	session:
	    store-type: redis
	redis:
	    host: 116.62.13.104

启动类

使用@EnableRedisHttpSession注解开启session共享

springcloud gateway session 保存到redis spring session redis key_spring

如果连接不上redis,设置以下配置并重启

redis-cli
CONFIG SET protected-mode no

springcloud gateway session 保存到redis spring session redis key_spring_02

主启动类添加@EnableSpringHttpSession注解

@EnableEurekaClient
@SpringBootApplication
//Fegin
@EnableFeignClients
//开启session共享
@EnableSpringHttpSession
public class UserApplication {
public static void main(String[] args) {
        SpringApplication.run(UserApplication.class);
}
@Bean
public BCryptPasswordEncoder encoder(){
return new BCryptPasswordEncoder();
}
}

测试

在8001服务登录以后

springcloud gateway session 保存到redis spring session redis key_导入session_03


Redis 中的用户信息

springcloud gateway session 保存到redis spring session redis key_spring_04

8002服务不需要登陆就可以执行

springcloud gateway session 保存到redis spring session redis key_spring_05


8003服务不需要登陆就可以执行

springcloud gateway session 保存到redis spring session redis key_spring_06


如果8001退出登录了以后

springcloud gateway session 保存到redis spring session redis key_导入session_07

再次调用8002、8003的方法就会被踢出

springcloud gateway session 保存到redis spring session redis key_spring_08

springcloud gateway session 保存到redis spring session redis key_导入session_09

Bug 使用Fegin调用Session失效

springcloud gateway session 保存到redis spring session redis key_ide_10


8001登陆认证

springcloud gateway session 保存到redis spring session redis key_.net_11


使用Fegin负载均衡调用Dept8002、Dept8003

springcloud gateway session 保存到redis spring session redis key_ide_12

springcloud gateway session 保存到redis spring session redis key_.net_13

原因:

微服务使用feign相互之间调用时,存在session丢失解决方法:

编写一个拦截器来实现Header的传递,也就是需要实现RequestInterceptor接口。com.lsh.service是Fegin调用其他服务的包路径

@Configuration
@EnableFeignClients(basePackages = "com.lsh.service")
public class FeignRequestIntercepter implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
//通过RequestContextHolder获取本地请求
//获取为空  需要关闭Fegin熔断
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null){
            System.out.println("requestAttributes为null");
return;
}
//获取本地线程绑定的请求对象
        HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
        System.out.println("获取本地线程绑定的请求对象:"+request);
//给请求模板附加本地线程头部信息,主要是cookie信息
        Enumeration headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()){
            String name =(String) headerNames.nextElement();
            System.out.println("name:"+name);
            requestTemplate.header(name,request.getHeader(name));
}
}
}

Bug RequestContextHolder.getRequestAttributes()获取值为null

springcloud gateway session 保存到redis spring session redis key_ide_14

解决方式一 关闭Fegin的熔断

需要关闭Fegin的熔断。

springcloud gateway session 保存到redis spring session redis key_ide_15

springcloud gateway session 保存到redis spring session redis key_导入session_16

解决方式二 调整隔离策略

方式一仅限于Feign不开启Hystrix支持时,当Feign开启Hystrix支持时,获取值为null。
所以将隔离策略设为SEMAPHORE即可。

hystrix.command.default.execution.isolation.strategy: SEMAPHORE但是:这样配置后,Feign可以正常工作。但该方案不是特别好。原因是Hystrix官方强烈建议使用THREAD作为隔离策略!
yml配置

feign:
  hystrix:
    enabled: false
# 如果idea不识别hystrix.command...这个配置    是因为没有加入到Spring管理容器,不影响使用
hystrix:
  command:
default:
      execution:
        isolation:
          strategy: SEMAPHORE

解决方式三 自定义并发策略

目前,Spring Cloud Sleuth以及Spring Security都通过这种方式传递 ThreadLocal 对象。

编写自定义并发策略比较简单,只需编写一个类,让其继承HystrixConcurrencyStrategy ,并重写wrapCallable 方法即可。

springcloud gateway session 保存到redis spring session redis key_spring_17


完整代码:

package com.lsh.config;

import com.netflix.hystrix.HystrixThreadPoolKey;
import com.netflix.hystrix.HystrixThreadPoolProperties;
import com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle;
import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier;
import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook;
import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher;
import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
import com.netflix.hystrix.strategy.properties.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author :LiuShihao
 * @date :Created in 2020/11/12 10:56 上午
 * @desc :自定义并发策略
 * 参考文章:http://www.itmuch.com/spring-cloud-sum/hystrix-threadlocal/
 */
@Slf4j
@Component
public class RequestAttributeHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {

private HystrixConcurrencyStrategy delegate;

public RequestAttributeHystrixConcurrencyStrategy() {
try {
            log.info("加载RequestAttributeHystrixConcurrencyStrategy");
this.delegate = HystrixPlugins.getInstance().getConcurrencyStrategy();
if (this.delegate instanceof RequestAttributeHystrixConcurrencyStrategy) {
// Welcome to singleton hell...
return;
}
            HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins
.getInstance().getCommandExecutionHook();
            HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance()
.getEventNotifier();
            HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance()
.getMetricsPublisher();
            HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance()
.getPropertiesStrategy();
this.logCurrentStateOfHystrixPlugins(eventNotifier, metricsPublisher,
                    propertiesStrategy);
            HystrixPlugins.reset();
            HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
            HystrixPlugins.getInstance()
.registerCommandExecutionHook(commandExecutionHook);
            HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
            HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
            HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
}
catch (Exception e) {
            log.error("Failed to register Sleuth Hystrix Concurrency Strategy", e);
}
}

private void logCurrentStateOfHystrixPlugins(HystrixEventNotifier eventNotifier,
                                                 HystrixMetricsPublisher metricsPublisher,
                                                 HystrixPropertiesStrategy propertiesStrategy) {
if (log.isDebugEnabled()) {
            log.debug("Current Hystrix plugins configuration is ["
+ "concurrencyStrategy [" + this.delegate + "]," + "eventNotifier ["
+ eventNotifier + "]," + "metricPublisher [" + metricsPublisher + "],"
+ "propertiesStrategy [" + propertiesStrategy + "]," + "]");
            log.debug("Registering Sleuth Hystrix Concurrency Strategy.");
}
}

@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
return new WrappedCallable<>(callable, requestAttributes);
}

@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                            HystrixProperty<Integer> corePoolSize,
                                            HystrixProperty<Integer> maximumPoolSize,
                                            HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
                                            BlockingQueue<Runnable> workQueue) {
return this.delegate.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize,
                keepAliveTime, unit, workQueue);
}

@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
                                            HystrixThreadPoolProperties threadPoolProperties) {
return this.delegate.getThreadPool(threadPoolKey, threadPoolProperties);
}

@Override
public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
return this.delegate.getBlockingQueue(maxQueueSize);
}

@Override
public <T> HystrixRequestVariable<T> getRequestVariable(
            HystrixRequestVariableLifecycle<T> rv) {
return this.delegate.getRequestVariable(rv);
}

static class WrappedCallable<T> implements Callable<T> {

private final Callable<T> target;
private final RequestAttributes requestAttributes;

public WrappedCallable(Callable<T> target, RequestAttributes requestAttributes) {
this.target = target;
this.requestAttributes = requestAttributes;
}

@Override
public T call() throws Exception {
try {
                RequestContextHolder.setRequestAttributes(requestAttributes);
return target.call();
}
finally {
                RequestContextHolder.resetRequestAttributes();
}
}
}
}

测试

User服务成功使用Fegin访问到Dept的两个服务

springcloud gateway session 保存到redis spring session redis key_ide_18

springcloud gateway session 保存到redis spring session redis key_.net_19