springcloud微服务中, 有些参数信息是要进行传输到不同进程服务的,但是当使用Hystrix做流量控制时,并且使用的是线程策略,那么这时在调用服务或者服务类时,由于进入了子线程,在原来父线程的上下文就获取不到了,这时就无法正常把如session信息等通过Feign调用传递请求头给提供方。
编写controller 在此写入上下文信息–> 调用服务层service 在此获取上下文 --> 结果是获取时报空指针
package cn.springcloud.controller;
import cn.springcloud.config.HystrixThreadLocal;
import cn.springcloud.service.HystrixService;
import cn.springcloud.service.ThreadLocalService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
@Controller
public class ThreadLocalController {
@Autowired
private ThreadLocalService threadLocalService;
@GetMapping("/getuser/{id}")
@ResponseBody
public Object getUser(@PathVariable Integer id){
HystrixThreadLocal.threadLocal.set("userId="+id);
StringBuffer sff = new StringBuffer();
RequestContextHolder.currentRequestAttributes().setAttribute("userId",
"userId="+id, RequestAttributes.SCOPE_REQUEST);
sff.append("Current thread---->"+Thread.currentThread());
sff.append(" ");
sff.append("HystrixThreadLocal----> userId: "+ HystrixThreadLocal.threadLocal.get());
sff.append(" ");
sff.append("RequestContextHolder---->"+RequestContextHolder.currentRequestAttributes()
.getAttribute("userId",RequestAttributes.SCOPE_REQUEST));
String user = threadLocalService.getUser(id);
return user+" "+sff.toString();
}
}``
注意,这里添加了@HystrixCommand,如果不添加时是正常的。
package cn.springcloud.service;
import cn.springcloud.config.HystrixThreadLocal;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
@Service
public class ThreadLocalService {
@HystrixCommand
public String getUser(Integer id){
System.out.println("Current thread:"+Thread.currentThread());
System.out.println("HystrixLocal:"+ HystrixThreadLocal.threadLocal.get());
Object userId = null;
try{
userId = RequestContextHolder.getRequestAttributes().getAttribute("userId", RequestAttributes.SCOPE_REQUEST);
}catch (Exception e){
System.out.println("error userid");
}
System.out.println("RequestContexHolder:"+ userId);
return "user:"+id;
}
}
测试结果:
查看相关的策略类可以看出RequestContextHolder保存的是ThreadLocal,另外,
在SecurityContextConcurrencyStrategy 下有一个它自己的方法org.springframework.cloud.netflix.hystrix.security.SecurityContextConcurrencyStrategy#wrapCallable,这个方法有创建一个DelegatingSecurityContextCallable,进入这个DelegatingSecurityContextCallable再看下它的org.springframework.security.concurrent.DelegatingSecurityContextCallable#call,从中可以看出有一个return delegate.call();动作。根据网上与相关书籍介绍,可以从这个delegate.call()前进行设置上下文,以同步到子线程。执行步骤如下:
创建SpringCloudHystrixConcurrencyStrategy 继承 HystrixConcurrencyStrategy --》 重写wrapCallable --》此方法返回一个新的类对象HystrixThreadCallable,实现了java.util.concurrent.Callable --》重写Callable的call方法 --》 在执行return delegate.call(); 前设置上下文。
注意事项:HystrixThreadLocal 是我另外增加的一个保存当前线程的信息类,这个可以根据自己情况,如果使用自己新增的线程保存类,则RequestContextHolder可以不使用都可以了。
这个可有可无,主要是用来保存自定义的thredlocal
package cn.springcloud.config;
public class HystrixThreadLocal {
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
}
这个是新增的策略类,用来控制增加上下文传输的逻辑,是必须的。
注意事项:这个类里面有一个内部类HystrixThreadCallable,用来执行call的
package cn.springcloud.config;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.Callable;
public class SpringCloudHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
return new HystrixThreadCallable<>(callable, RequestContextHolder.getRequestAttributes());
}
static class HystrixThreadCallable<T> implements Callable<T>{
private final Callable<T> delegate;
private final RequestAttributes requestAttributes;
public HystrixThreadCallable(Callable<T> callable, RequestAttributes requestAttributes) {
this.delegate = callable;
this.requestAttributes = requestAttributes;
}
@Override
public T call() throws Exception {
try{
RequestContextHolder.setRequestAttributes(requestAttributes);
HystrixThreadLocal.threadLocal.set(requestAttributes.getAttribute("userId",RequestAttributes.SCOPE_REQUEST).toString());
return delegate.call();
}finally {
RequestContextHolder.getRequestAttributes();
}
}
}
}
把新增的策略类SpringCloudHystrixConcurrencyStrategy注册到HystrixPlugins
@PostConstruct
public void init(){
HystrixPlugins.getInstance().registerConcurrencyStrategy(new SpringCloudHystrixConcurrencyStrategy());
}
从下图可以看出,前后的信息一致了。
问题如下图
这个问题是我在《重新定义SpringCloud实战》中看到的,但是文章上说只能被调用一次,但是测试多个客户端多次发起请求都没测试出这个问题,所以这里顺便贴出来。代大家验证。
同时也希望大家能提供验证的方式以供参考,谢谢!
因篇幅问题不能全部显示,请点此查看更多更全内容