SpringCloud带给我们的便利是有目共睹的,它能快速的帮助中小型企业构建微服务的程序架构。其背后有SpringBoot这员大将做底层支持,同时得力于Spring在java界的影响力和近几年 micro service
的流行,因此SpringCloud从2016年渐渐的走进人们的视野。可是便捷这个东西确实是一把双刃剑,我们在享受便捷的同时也要做好难以排错的准备,同时在版本升级的时候一定慎之又慎。
相关源码浅析
对于我们的
feign
项目,如果加上了
spring-cloud-starter-netflix-hystrix
,那么
feign
自动会将所有的方法用
hystrix
进行包装,这是怎么实现的呢?答案就是
代理模式
。
HystrixFeign
我们先来看看这个API文档注释:
Allows Feign interfaces to return HystrixCommand or rx.Observable or rx.Single objects. Also decorates normal Feign methods with circuit breakers, but calls {@linkHystrixCommand#execute()} directly.
根据API文档解释,这个类允许Feign接口返回
HystrixCommand
或者
rx.Observable
rx.Single objects
,同时将
Feign
的method通过断路器进行包装
public final class HystrixFeign {
public static Builder builder() {
return new Builder();
}
public static final class Builder extends Feign.Builder {
private Contract contract = new Contract.Default();
//设置HystrixCommand的Setter属性工厂
private SetterFactory setterFactory = new SetterFactory.Default();
/**
允许覆盖Hystrix的一些属性比如说threadpool和commandkey等
* Allows you to override hystrix properties such as thread pools and command keys.
*/
public Builder setterFactory(SetterFactory setterFactory) {
this.setterFactory = setterFactory;
return this;
}
//...省略其他代码
@Override
public Feign build() {
return build(null);
}
/** Configures components needed for hystrix integration. */
Feign build(final FallbackFactory<?> nullableFallbackFactory) {
super.invocationHandlerFactory(new InvocationHandlerFactory() {
@Override public InvocationHandler create(Target target,
Map<Method, MethodHandler> dispatch) {
//实际创建的是HystrixInvocatonHandler代理对象
return new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory);
}
});
super.contract(new HystrixDelegatingContract(contract));
return super.build();
}
根据上述代码,我们可以看到在
build
方法里实际创建的是
HystrixInvocationHandler
对象。
HystrixInvocationHandler
/**
实现InvocationHandler接口,通过代理模式实现
*/
final class HystrixInvocationHandler implements InvocationHandler {
private final Target<?> target;
private final Map<Method, MethodHandler> dispatch;
private final FallbackFactory<?> fallbackFactory; // Nullable
private final Map<Method, Method> fallbackMethodMap;
private final Map<Method, Setter> setterMethodMap;
HystrixInvocationHandler(Target<?> target, Map<Method, MethodHandler> dispatch,
SetterFactory setterFactory, FallbackFactory<?> fallbackFactory) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch");
this.fallbackFactory = fallbackFactory;
this.fallbackMethodMap = toFallbackMethod(dispatch);
this.setterMethodMap = toSetters(setterFactory, target, dispatch.keySet());
}
//...省略部分代码
/**
* 通过代码可知:每个method方法都对应独立的setter配置
* Process all methods in the target so that appropriate setters are created.
*/
static Map<Method, Setter> toSetters(SetterFactory setterFactory, Target<?> target,
Set<Method> methods) {
Map<Method, Setter> result = new LinkedHashMap<Method, Setter>();
for (Method method : methods) {
method.setAccessible(true);
result.put(method, setterFactory.create(target, method));
}
return result;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
throws Throwable {
//解决 Object方法中 ReflectiveFeign.FeignInvocationHandler中的方法冲突问题
// 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对象
HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) {
@Override
protected Object run() throws Exception {
try {
//代理执行,用HystrixCommand包装Feign的请求
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 {
return result;
}
} catch (IllegalAccessException e) {
// shouldn't happen as method is public due to being an interface
throw new AssertionError(e);
} catch (InvocationTargetException e) {
// Exceptions on fallback are tossed by Hystrix
throw new AssertionError(e.getCause());
}
}
};
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();
}
return hystrixCommand.execute();
}
//省略部分代码...
}
通过上述代码可以发现这里使用代理模式将
Hystrix
包装
Feign
SetterFactory
SetterFactory
是用于生成
Hystrix
Setter配置项的工厂:
public interface SetterFactory {
/**
* Returns a hystrix setter appropriate for the given target and method
*/
HystrixCommand.Setter create(Target<?> target, Method method);
/**
* Default behavior is to derive the group key from {@link Target#name()} and the command key from
* {@link Feign#configKey(Class, Method)}.
*/
final class Default implements SetterFactory {
@Override
public HystrixCommand.Setter create(Target<?> target, Method method) {
String groupKey = target.name();
String commandKey = Feign.configKey(target.type(), method);
return HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
.andCommandKey(HystrixCommandKey.Factory.asKey(commandKey));
}
}
}
根据提示它对应的
commandKey
通过
Feign#configKey()
生成,而
groupKey
是接口名
下面我们就看看
Feign
怎么样生成key的:
public abstract class Feign {
/**
匹配基本规则:接口名#方法名(参数类型)
Route53->route53.Route53
Route53#list() -> route53.Route53#list()
Route53#listAt(Marker) -> route53.Route53#listAt(Marker)
Route53#listByNameAndType(String, String) -> route53.Route53#listAt(String, String)
*/
public static String configKey(Class targetType, Method method) {
StringBuilder builder = new StringBuilder();
builder.append(targetType.getSimpleName());
builder.append('#').append(method.getName()).append('(');
for (Type param : method.getGenericParameterTypes()) {
param = Types.resolve(targetType, targetType, param);
builder.append(Types.getRawType(param).getSimpleName()).append(',');
}
if (method.getParameterTypes().length > 0) {
builder.deleteCharAt(builder.length() - 1);
}
return builder.append(')').toString();
}
}
到此处我们可以断定
Feign
与
Hystrix
集成时配置就可以这么写:
hystrix:
command:
Timeout#timeout():
execution:
isolation:
thread:
timeoutInMilliseconds: 20000
对应的接口:
package com.zhshop.web;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient("purchase-service")
public interface Timeout {
@RequestMapping("/feignTest")
public String timeout();
}
Hystrix的配置类
HystrixCommandProperties
基本上包含了所有Hystrix的默认配置,代码片段如下:
public abstract class HystrixCommandProperties {
//传入前缀hystrix
protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder) {
this(key, builder, "hystrix");
}
// known that we're using deprecated HystrixPropertiesChainedServoProperty until ChainedDynamicProperty exists in Archaius
protected HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix) {
this.key = key;
this.circuitBreakerEnabled = getProperty(propertyPrefix, key, "circuitBreaker.enabled", builder.getCircuitBreakerEnabled(), default_circuitBreakerEnabled);
this.circuitBreakerRequestVolumeThreshold = getProperty(propertyPrefix, key, "circuitBreaker.requestVolumeThreshold", builder.getCircuitBreakerRequestVolumeThreshold(), default_circuitBreakerRequestVolumeThreshold);
this.circuitBreakerSleepWindowInMilliseconds = getProperty(propertyPrefix, key, "circuitBreaker.sleepWindowInMilliseconds", builder.getCircuitBreakerSleepWindowInMilliseconds(), default_circuitBreakerSleepWindowInMilliseconds);
this.circuitBreakerErrorThresholdPercentage = getProperty(propertyPrefix, key, "circuitBreaker.errorThresholdPercentage", builder.getCircuitBreakerErrorThresholdPercentage(), default_circuitBreakerErrorThresholdPercentage);
this.circuitBreakerForceOpen = getProperty(propertyPrefix, key, "circuitBreaker.forceOpen", builder.getCircuitBreakerForceOpen(), default_circuitBreakerForceOpen);
this.circuitBreakerForceClosed = getProperty(propertyPrefix, key, "circuitBreaker.forceClosed", builder.getCircuitBreakerForceClosed(), default_circuitBreakerForceClosed);
this.executionIsolationStrategy = getProperty(propertyPrefix, key, "execution.isolation.strategy", builder.getExecutionIsolationStrategy(), default_executionIsolationStrategy);
//this property name is now misleading. //TODO figure out a good way to deprecate this property name
this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);
this.executionTimeoutEnabled = getProperty(propertyPrefix, key, "execution.timeout.enabled", builder.getExecutionTimeoutEnabled(), default_executionTimeoutEnabled);
this.executionIsolationThreadInterruptOnTimeout = getProperty(propertyPrefix, key, "execution.isolation.thread.interruptOnTimeout", builder.getExecutionIsolationThreadInterruptOnTimeout(), default_executionIsolationThreadInterruptOnTimeout);
this.executionIsolationThreadInterruptOnFutureCancel = getProperty(propertyPrefix, key, "execution.isolation.thread.interruptOnFutureCancel", builder.getExecutionIsolationThreadInterruptOnFutureCancel(), default_executionIsolationThreadInterruptOnFutureCancel);
this.executionIsolationSemaphoreMaxConcurrentRequests = getProperty(propertyPrefix, key, "execution.isolation.semaphore.maxConcurrentRequests", builder.getExecutionIsolationSemaphoreMaxConcurrentRequests(), default_executionIsolationSemaphoreMaxConcurrentRequests);
this.fallbackIsolationSemaphoreMaxConcurrentRequests = getProperty(propertyPrefix, key, "fallback.isolation.semaphore.maxConcurrentRequests", builder.getFallbackIsolationSemaphoreMaxConcurrentRequests(), default_fallbackIsolationSemaphoreMaxConcurrentRequests);
this.fallbackEnabled = getProperty(propertyPrefix, key, "fallback.enabled", builder.getFallbackEnabled(), default_fallbackEnabled);
this.metricsRollingStatisticalWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingStats.timeInMilliseconds", builder.getMetricsRollingStatisticalWindowInMilliseconds(), default_metricsRollingStatisticalWindow);
this.metricsRollingStatisticalWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingStats.numBuckets", builder.getMetricsRollingStatisticalWindowBuckets(), default_metricsRollingStatisticalWindowBuckets);
this.metricsRollingPercentileEnabled = getProperty(propertyPrefix, key, "metrics.rollingPercentile.enabled", builder.getMetricsRollingPercentileEnabled(), default_metricsRollingPercentileEnabled);
this.metricsRollingPercentileWindowInMilliseconds = getProperty(propertyPrefix, key, "metrics.rollingPercentile.timeInMilliseconds", builder.getMetricsRollingPercentileWindowInMilliseconds(), default_metricsRollingPercentileWindow);
this.metricsRollingPercentileWindowBuckets = getProperty(propertyPrefix, key, "metrics.rollingPercentile.numBuckets", builder.getMetricsRollingPercentileWindowBuckets(), default_metricsRollingPercentileWindowBuckets);
this.metricsRollingPercentileBucketSize = getProperty(propertyPrefix, key, "metrics.rollingPercentile.bucketSize", builder.getMetricsRollingPercentileBucketSize(), default_metricsRollingPercentileBucketSize);
this.metricsHealthSnapshotIntervalInMilliseconds = getProperty(propertyPrefix, key, "metrics.healthSnapshot.intervalInMilliseconds", builder.getMetricsHealthSnapshotIntervalInMilliseconds(), default_metricsHealthSnapshotIntervalInMilliseconds);
this.requestCacheEnabled = getProperty(propertyPrefix, key, "requestCache.enabled", builder.getRequestCacheEnabled(), default_requestCacheEnabled);
this.requestLogEnabled = getProperty(propertyPrefix, key, "requestLog.enabled", builder.getRequestLogEnabled(), default_requestLogEnabled);
// threadpool doesn't have a global override, only instance level makes sense
this.executionIsolationThreadPoolKeyOverride = forString().add(propertyPrefix + ".command." + key.name() + ".threadPoolKeyOverride", null).build();
}
//处理布尔类型的值
private static HystrixProperty<Boolean> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Boolean builderOverrideValue, Boolean defaultValue) {
return forBoolean()
.add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue)
.add(propertyPrefix + ".command.default." + instanceProperty, defaultValue)
.build();
}
//处理Integer类型的值
private static HystrixProperty<Integer> getProperty(String propertyPrefix, HystrixCommandKey key, String instanceProperty, Integer builderOverrideValue, Integer defaultValue) {
return forInteger()
.add(propertyPrefix + ".command." + key.name() + "." + instanceProperty, builderOverrideValue)
.add(propertyPrefix + ".command.default." + instanceProperty, defaultValue)
.build();
}
//省略部分代码.....
}
在这里我们可以看到默认情况下会传入前缀:
hystrix
,然后拼接
command
+
default
|
key
的方式,这里的key通常情况下传的是
-
对应Feign
的方法Feign#configKey()
-
通常情况下是zuul
即服务名称serviceId
那么最终
Hystrix
的默认配置为:
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 1000
FeignClientsProperties
根据官网描述,我们可以通过配置的方式来配置Feign的客户端了:
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
那么与之对应的是在
FeignClientProperties
这个类里
package org.springframework.cloud.openfeign;
import feign.Contract;
import feign.Logger;
import feign.RequestInterceptor;
import feign.Retryer;
import feign.codec.Decoder;
import feign.codec.Encoder;
import feign.codec.ErrorDecoder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* @author Eko Kurniawan Khannedy
*/
@ConfigurationProperties("feign.client")
public class FeignClientProperties {
private boolean defaultToProperties = true;
//默认名称就是default
private String defaultConfig = "default";
// feign.client.config.default.xxx的配置映射此处 这里的String通常是客户端名(@FeignClient(名称))
private Map<String, FeignClientConfiguration> config = new HashMap<>();
public boolean isDefaultToProperties() {
return defaultToProperties;
}
public void setDefaultToProperties(boolean defaultToProperties) {
this.defaultToProperties = defaultToProperties;
}
public String getDefaultConfig() {
return defaultConfig;
}
public void setDefaultConfig(String defaultConfig) {
this.defaultConfig = defaultConfig;
}
public Map<String, FeignClientConfiguration> getConfig() {
return config;
}
public void setConfig(Map<String, FeignClientConfiguration> config) {
this.config = config;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FeignClientProperties that = (FeignClientProperties) o;
return defaultToProperties == that.defaultToProperties &&
Objects.equals(defaultConfig, that.defaultConfig) &&
Objects.equals(config, that.config);
}
@Override
public int hashCode() {
return Objects.hash(defaultToProperties, defaultConfig, config);
}
//最终的配置映射到该类上
public static class FeignClientConfiguration {
private Logger.Level loggerLevel;
private Integer connectTimeout;
private Integer readTimeout;
private Class<Retryer> retryer;
private Class<ErrorDecoder> errorDecoder;
private List<Class<RequestInterceptor>> requestInterceptors;
private Boolean decode404;
private Class<Decoder> decoder;
private Class<Encoder> encoder;
private Class<Contract> contract;
public Logger.Level getLoggerLevel() {
return loggerLevel;
}
public void setLoggerLevel(Logger.Level loggerLevel) {
this.loggerLevel = loggerLevel;
}
public Integer getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(Integer connectTimeout) {
this.connectTimeout = connectTimeout;
}
public Integer getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(Integer readTimeout) {
this.readTimeout = readTimeout;
}
public Class<Retryer> getRetryer() {
return retryer;
}
public void setRetryer(Class<Retryer> retryer) {
this.retryer = retryer;
}
public Class<ErrorDecoder> getErrorDecoder() {
return errorDecoder;
}
public void setErrorDecoder(Class<ErrorDecoder> errorDecoder) {
this.errorDecoder = errorDecoder;
}
public List<Class<RequestInterceptor>> getRequestInterceptors() {
return requestInterceptors;
}
public void setRequestInterceptors(List<Class<RequestInterceptor>> requestInterceptors) {
this.requestInterceptors = requestInterceptors;
}
public Boolean getDecode404() {
return decode404;
}
public void setDecode404(Boolean decode404) {
this.decode404 = decode404;
}
public Class<Decoder> getDecoder() {
return decoder;
}
public void setDecoder(Class<Decoder> decoder) {
this.decoder = decoder;
}
public Class<Encoder> getEncoder() {
return encoder;
}
public void setEncoder(Class<Encoder> encoder) {
this.encoder = encoder;
}
public Class<Contract> getContract() {
return contract;
}
public void setContract(Class<Contract> contract) {
this.contract = contract;
}
//省略部分代码...
}
FeignClientFactoryBean
FeignClient最终是被
FeignClientFactoryBean
创建,我们可以看一下它的
getObject()
方法:
@Override
public Object getObject() throws Exception {
FeignContext context = applicationContext.getBean(FeignContext.class);
Feign.Builder builder = feign(context);
if (!StringUtils.hasText(this.url)) {
String url;
if (!this.name.startsWith("http")) {
url = "http://" + this.name;
}
else {
url = this.name;
}
url += cleanPath();
return loadBalance(builder, context, new HardCodedTarget<>(this.type,
this.name, url));
}
if (StringUtils.hasText(this.url) && !this.url.startsWith("http")) {
this.url = "http://" + this.url;
}
String url = this.url + cleanPath();
Client client = getOptional(context, Client.class);
if (client != null) {
if (client instanceof LoadBalancerFeignClient) {
// not lod balancing because we have a url,
// but ribbon is on the classpath, so unwrap
client = ((LoadBalancerFeignClient)client).getDelegate();
}
builder.client(client);
}
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, new HardCodedTarget<>(
this.type, this.name, url));
}
在这里
Targeter
接口是获取
Feign
的目标地址对象的,同时这里还会创建具备负载均衡能力的
Feign
,而
Feign
要与
Hystrix
集成,
HystrixTargeter
起到了关键作用:
/*
* Copyright 2013-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.cloud.openfeign;
import org.springframework.util.Assert;
import feign.Feign;
import feign.Target;
import feign.hystrix.FallbackFactory;
import feign.hystrix.HystrixFeign;
import feign.hystrix.SetterFactory;
/**
* @author Spencer Gibb
* @author Erik Kringen
*/
@SuppressWarnings("unchecked")
class HystrixTargeter implements Targeter {
@Override
public <T> T target(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,
Target.HardCodedTarget<T> target) {
if (!(feign instanceof feign.hystrix.HystrixFeign.Builder)) {
return feign.target(target);
}
feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;
SetterFactory setterFactory = getOptional(factory.getName(), context,
SetterFactory.class);
if (setterFactory != null) {
builder.setterFactory(setterFactory);
}
Class<?> fallback = factory.getFallback();
if (fallback != void.class) {
return targetWithFallback(factory.getName(), context, target, builder, fallback);
}
Class<?> fallbackFactory = factory.getFallbackFactory();
if (fallbackFactory != void.class) {
return targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);
}
return feign.target(target);
}
private <T> T targetWithFallback(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target,
HystrixFeign.Builder builder, Class<?> fallback) {
//....
}
private <T> T targetWithFallbackFactory(String feignClientName, FeignContext context,
Target.HardCodedTarget<T> target,
HystrixFeign.Builder builder,
Class<?> fallbackFactoryClass) {
//...
}
}