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) {
//...
}
}