问题再现:
在使用open fegin的Hystrix情况下,将当前线程请求传到下游时,发现从RequestContextHolder中获取到的HttpServletRequest为空。
@Override
public void apply(RequestTemplate requestTemplate) {
logger.info("执行Feign拦截器,处理请求头。。。");
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (Objects.nonNull(requestAttributes)) {
HttpServletRequest request = requestAttributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()){
String nextElement = headerNames.nextElement();
logger.info("Header[{}={}]", nextElement, request.getHeader(nextElement));
requestTemplate.header(nextElement, request.getHeader(nextElement));
}
}
}
原因分析
RequestContextHolder请求上下文持有者,我们可以在当前线程的任意位置,通过这个类来获取到当前请求的RequestAttributes,但是有个问题,其请求对象是保存在ThreadLocal中的,我们Hystrix去请求另一个服务接口是通过重开子线程的,因此我们子线程中想要获取RequestAttributes自然是获取不到的。
public abstract class RequestContextHolder {
private static final boolean jsfPresent =
ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
//...省略...
}
HystrixInvocationHandler处理器中可以看到,它是利用子线程来请求的。
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
// early exit if the invoked method is from java.lang.Object
// code is the same as ReflectiveFeign.FeignInvocationHandler
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
HystrixCommand<Object> hystrixCommand =
new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
try {
return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
} catch (Exception e) {
throw e;
} catch (Throwable t) {
throw (Error) t;
}
}
@Override
protected Object getFallback() {
if (fallbackFactory == null) {
return super.getFallback();
}
try {
Object fallback = fallbackFactory.create(getExecutionException());
Object result = fallbackMethodMap.get(method).invoke(fallback, args);
if (isReturnsHystrixCommand(method)) {
return ((HystrixCommand) result).execute();
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return ((Observable) result).toBlocking().first();
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return ((Single) result).toObservable().toBlocking().first();
} else if (isReturnsCompletable(method)) {
((Completable) result).await();
return null;
} else if (isReturnsCompletableFuture(method)) {
return ((Future) result).get();
} else {
return result;
}
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an interface
throw new AssertionError(e);
} catch (InvocationTargetException | ExecutionException e) {
// Exceptions on fallback are tossed by Hystrix
throw new AssertionError(e.getCause());
} catch (InterruptedException e) {
// Exceptions on fallback are tossed by Hystrix
Thread.currentThread().interrupt();
throw new AssertionError(e.getCause());
}
}
};
if (Util.isDefault(method)) {
return hystrixCommand.execute();
} else if (isReturnsHystrixCommand(method)) {
return hystrixCommand;
} else if (isReturnsObservable(method)) {
// Create a cold Observable
return hystrixCommand.toObservable();
} else if (isReturnsSingle(method)) {
// Create a cold Observable as a Single
return hystrixCommand.toObservable().toSingle();
} else if (isReturnsCompletable(method)) {
return hystrixCommand.toObservable().toCompletable();
} else if (isReturnsCompletableFuture(method)) {
return new ObservableCompletableFuture<>(hystrixCommand);
}
return hystrixCommand.execute();
}
解决方案
一、修改Hystrix隔离策略(不推荐)
Hystrix提供了两个隔离策略:THREAD、SEMAPHORE。其默认的策略为THREAD(线程池)。
修改隔离策略为SEMAPHORE,这个方案就不说了,毕竟官网也不推荐使用这种模式!
二、自定义隔离策略(推荐)
默认策略实现类为HystrixConcurrencyStrategyDefault,可以看看它源码,基本就是空对象。我们这里不用它这个默认的实现类,自己实现HystrixConcurrencyStrategy类,重写其#wrapCallable方法,方法内逻辑就是重开子线程之前将主线程的请求设置为可被子线程继承**(注意:继承的请求还是同一个,如果主线程和子线程存在异步情况,主线程结束后请求被销毁,子线程同样获取不到了)**。上代码:
package com.pcliu.platform.setting.openfeign;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import java.util.concurrent.Callable;
/**
* @author pcliu
* @version 1.0
* @date 2022/6/30 17:56
*/
@Component
public class RequestAttributeHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
public static final Logger logger = LoggerFactory.getLogger(RequestAttributeHystrixConcurrencyStrategy.class);
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
RequestAttributes currentRequestAttributes = RequestContextHolder.currentRequestAttributes();
RequestContextHolder.setRequestAttributes(currentRequestAttributes, Boolean.TRUE);
logger.info("子线程继承父线程请求");
return callable;
}
}
还没有完,光实现了还不行,还得用上,很简单,bootstrap.yml添加个配置项:
hystrix:
plugin:
HystrixConcurrencyStrategy:
implementation: com.pcliu.platform.setting.openfeign.RequestAttributeHystrixConcurrencyStrategy
OK~这里就可以了
自定义隔离策略的思路
HystrixPlugins插件在获取当前策略时,是会先加载程序员配置的策略实现类,找不到才会加载默认策略实现。
public HystrixConcurrencyStrategy getConcurrencyStrategy() {
if (concurrencyStrategy.get() == null) {
// check for an implementation from Archaius first
Object impl = getPluginImplementation(HystrixConcurrencyStrategy.class);
if (impl == null) {
// nothing set via Archaius so initialize with default
concurrencyStrategy.compareAndSet(null, HystrixConcurrencyStrategyDefault.getInstance());
// we don't return from here but call get() again in case of thread-race so the winner will always get returned
} else {
// we received an implementation from Archaius so use it
concurrencyStrategy.compareAndSet(null, (HystrixConcurrencyStrategy) impl);
}
}
return concurrencyStrategy.get();
}
看这个方法==Object impl = getPluginImplementation(HystrixConcurrencyStrategy.class);,然后进入这个方法T p = getPluginImplementationViaProperties(pluginClass, dynamicProperties);,这里就是找程序员配置的策略实现类,然后加载进来:String propertyName = “hystrix.plugin.” + classSimpleName + “.implementation”;。除了可以定义策略插件,也可以定义其他各种插件。
private <T> T getPluginImplementation(Class<T> pluginClass) {
T p = getPluginImplementationViaProperties(pluginClass, dynamicProperties);
if (p != null) return p;
return findService(pluginClass, classLoader);
}
private static <T> T getPluginImplementationViaProperties(Class<T> pluginClass, HystrixDynamicProperties dynamicProperties) {
String classSimpleName = pluginClass.getSimpleName();
// Check Archaius for plugin class.
String propertyName = "hystrix.plugin." + classSimpleName + ".implementation";
String implementingClass = dynamicProperties.getString(propertyName, null).get();
if (implementingClass != null) {
try {
Class<?> cls = Class.forName(implementingClass);
// narrow the scope (cast) to the type we're expecting
cls = cls.asSubclass(pluginClass);
return (T) cls.newInstance();
} catch (ClassCastException e) {
throw new RuntimeException(classSimpleName + " implementation is not an instance of " + classSimpleName + ": " + implementingClass);
} catch (ClassNotFoundException e) {
throw new RuntimeException(classSimpleName + " implementation class not found: " + implementingClass, e);
} catch (InstantiationException e) {
throw new RuntimeException(classSimpleName + " implementation not able to be instantiated: " + implementingClass, e);
} catch (IllegalAccessException e) {
throw new RuntimeException(classSimpleName + " implementation not able to be accessed: " + implementingClass, e);
}
} else {
return null;
}
}
拓展
其实分析这个方法==Object impl = getPluginImplementation(HystrixConcurrencyStrategy.class);,如果程序员没有配置插件实现类,它最后返回的是return findService(pluginClass, classLoader);,翻看代码发现,其实它还提供了SPI的方式来加载自定义插件。
private static <T> T findService(
Class<T> spi,
ClassLoader classLoader) throws ServiceConfigurationError {
ServiceLoader<T> sl = ServiceLoader.load(spi,
classLoader);
for (T s : sl) {
if (s != null)
return s;
}
return null;
}
到此结束~