专栏系列文章:SpringCloud系列专栏
系列文章:
SpringCloud 源码系列(1)— 注册中心Eureka 之 启动初始化
SpringCloud 源码系列(2)— 注册中心Eureka 之 服务注册、续约
SpringCloud 源码系列(3)— 注册中心Eureka 之 抓取注册表
SpringCloud 源码系列(4)— 注册中心Eureka 之 服务下线、故障、自我保护机制
SpringCloud 源码系列(5)— 注册中心Eureka 之 EurekaServer集群
SpringCloud 源码系列(6)— 注册中心Eureka 之 总结篇
SpringCloud 源码系列(7)— 负载均衡Ribbon 之 RestTemplate
SpringCloud 源码系列(8)— 负载均衡Ribbon 之 核心原理
SpringCloud 源码系列(9)— 负载均衡Ribbon 之 核心组件与配置
SpringCloud 源码系列(10)— 负载均衡Ribbon 之 HTTP客户端组件
SpringCloud 源码系列(11)— 负载均衡Ribbon 之 重试与总结篇
SpringCloud 源码系列(12)— 服务调用Feign 之 基础使用篇
SpringCloud 源码系列(13)— 服务调用Feign 之 扫描@FeignClient注解接口
SpringCloud 源码系列(14)— 服务调用Feign 之 构建@FeignClient接口动态代理
前一篇文章已经分析出,最终在 Feign.Builder
的 build()
方法构建了 ReflectiveFeign
,然后利用 ReflectiveFeign 的 newInstance
方法创建了动态代理。这个动态代理的代理对象是 ReflectiveFeign.FeignInvocationHandler
。最终来说肯定就会利用 Client
进行负载均衡的请求。这节就来看看 Feign 如果利用动态代理发起HTTP请求的。
FeignClient 动态代理请求
使用 FeignClient 接口时,注入的其实是动态代理对象,调用接口方法时就会进入执行器 ReflectiveFeign.FeignInvocationHandler
,从 FeignInvocationHandler 的 invoke
方法可以看出,就是根据 method 获取要执行的方法处理器 MethodHandler
,然后执行方法。MethodHandler 的实际类型就是 SynchronousMethodHandler
。
static class FeignInvocationHandler implements InvocationHandler {
private final Target target;
private final Map<Method, MethodHandler> dispatch;
FeignInvocationHandler(Target target, Map<Method, MethodHandler> dispatch) {
this.target = checkNotNull(target, "target");
this.dispatch = checkNotNull(dispatch, "dispatch for %s", target);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//...
// 根据 method 获取 MethodHandler,然后执行方法
return dispatch.get(method).invoke(args);
}
}
接着看 SynchronousMethodHandler 的 invoke 方法,核心逻辑就两步:
- 先根据请求参数构建请求模板
RequestTemplate
,就是处理 URI 模板、参数,比如替换掉 uri 中的占位符、拼接参数等。 - 然后调用了
executeAndDecode
执行请求,并将相应结果解码返回。
public Object invoke(Object[] argv) throws Throwable {
// 构建请求模板,例如有 url 参数,请求参数之类的
RequestTemplate template = buildTemplateFromArgs.create(argv);
Options options = findOptions(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
// 执行并解码
return executeAndDecode(template, options);
} catch (RetryableException e) {
// 重试,默认是从不重试
try {
retryer.continueOrPropagate(e);
} catch (RetryableException th) {
Throwable cause = th.getCause();
if (propagationPolicy == UNWRAP && cause != null) {
throw cause;
} else {
throw th;
}
}
continue;
}
}
}
可以看到,经过处理后,URI 上的占位符就被参数替换了,并且拼接了请求参数。
执行请求和解码
接着看 executeAndDecode
,主要有三步:
- 先调用
targetRequest
方法,主要就是遍历RequestInterceptor
对请求模板 RequestTemplate 定制化,然后调用HardCodedTarget
的target
方法将 RequestTemplate 转换成Request
请求对象,Request 封装了请求地址、请求头、body 等信息。 - 然后使用客户端 client 来执行请求,就是
LoadBalancerFeignClient
,这里就进入了负载均衡请求了。 - 最后用解码器
decoder
来解析响应结果,将结果转换成接口的返回类型。
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
// 处理RequestTemplate,得到请求对象 Request
Request request = targetRequest(template);
Response response;
try {
// 调用 client 执行请求,client => LoadBalancerFeignClient
response = client.execute(request, options);
// 构建响应 Response
response = response.toBuilder()
.request(request)
.requestTemplate(template)
.build();
} catch (IOException e) {
//...
}
if (decoder != null) {
// 使用解码器解码,将返回数据转换成接口的返回类型
return decoder.decode(response, metadata.returnType());
}
//....
}
// 应用拦截器处理 RequestTemplate,最后使用 target 从 RequestTemplate 中得到 Request
Request targetRequest(RequestTemplate template) {
for (RequestInterceptor interceptor : requestInterceptors) {
interceptor.apply(template);
}
// target => HardCodedTarget
return target.apply(template);
}
HardCodedTarget
是硬编码写死的,我们没有办法定制化,看下它的 apply
方法,主要就是处理 RequestTemplate 模板的地址,生成完成的请求地址。最后返回 Request 请求对象。
public Request apply(RequestTemplate input) {
if (input.url().indexOf("http") != 0) {
// url() => http://demo-producer
// input.target 处理请求模板
input.target(url());
}
return input.request();
}
可以看到经过 HardCodedTarget 的 apply 方法之后,就拼接上了 url 前缀了。
LoadBalancerFeignClient 负载均衡
LoadBalancerFeignClient
是 Feign 实现负载均衡核心的组件,是 Feign 网络请求组件 Client
的默认实现,LoadBalancerFeignClient 最后是使用 FeignLoadBalancer
来进行负载均衡的请求。
看 LoadBalancerFeignClient 的 execute
方法,从这里到后面执行负载均衡请求,其实跟分析 Ribbon 源码中 RestTemplate 的负载均衡请求都是类似的了。
- 可以看到也是先将请求封装到 ClientRequest,实现类是
FeignLoadBalancer.RibbonRequest
。注意 RibbonRequest 第一个参数 Client 就是设置的 LoadBalancerFeignClient 的代理对象,启用 apache httpclient 时,就是ApacheHttpClient
。 - 然后获取客户端配置,也就是说
Ribbon 的客户端配置对 Feign 同样生效
。 - 最后获取了负载均衡器
FeignLoadBalancer
,然后执行负载均衡请求。
public Response execute(Request request, Request.Options options) throws IOException {
try {
URI asUri = URI.create(request.url());
// 客户端名称:demo-producer
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
// 封装 ClientRequest => FeignLoadBalancer.RibbonRequest
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 客户端负载均衡配置 ribbon.demo-producer.*
IClientConfig requestConfig = getClientConfig(options, clientName);
// lbClient => 负载均衡器 FeignLoadBalancer,执行负载均衡请求
return lbClient(clientName)
.executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
}
catch (ClientException e) {
//...
}
}
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
进入 executeWithLoadBalancer
方法,这就跟 Ribbon 源码中分析的是一样的了,最终就验证了 Feign 基于 Ribbon 来做负载均衡请求。
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
// 负载均衡器执行命令
LoadBalancerCommand<T> command = buildLoadBalancerCommand(request, requestConfig);
try {
return command.submit(
new ServerOperation<T>() {
@Override
public Observable<T> call(Server server) {
// 用Server的信息重构URI地址
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
// 实际调用 LoadBalancerFeignClient 的 execute 方法
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
//....
}
}
重构URI后,实际是调用 FeignLoadBalancer 的 execute
方法来执行最终的HTTP调用的。看下 FeignLoadBalancer 的 execute 方法,最终来说,就是使用代理的HTTP客户端来执行请求。
默认情况下,就是 Client.Default
,用 HttpURLConnection 执行HTTP请求;启用了 httpclient 后,就是 ApacheHttpClient;启用了 okhttp,就是 OkHttpClient。
这里有一点需要注意的是,FeignClient 虽然可以配置超时时间,但进入 FeignLoadBalancer 的 execute 方法后,可以看到会用 Ribbon 的超时时间覆盖 Feign 配置的超时时间
,最终以 Ribbon 的超时时间为准。
public RibbonResponse execute(RibbonRequest request, IClientConfig configOverride) throws IOException {
Request.Options options;
if (configOverride != null) {
// 用 Ribbon 的超时时间覆盖了feign配置的超时时间
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Request.Options(override.connectTimeout(this.connectTimeout),
override.readTimeout(this.readTimeout));
}
else {
options = new Request.Options(this.connectTimeout, this.readTimeout);
}
// request.client() HTTP客户端对象
Response response = request.client().execute(request.toRequest(), options);
return new RibbonResponse(request.getUri(), response);
}
一张图总结 Feign 负载均衡请求
关于Ribbon的源码分析请看前面 Ribbon 相关的文章,Ribbon 如何从 eureka 注册中心获取 Server 就不再分析了。
下面这张图总结了 Feign 负载均衡请求的流程:
- 首先服务启动的时候会扫描解析
@FeignClient
注解的接口,并生成代理类注入到容器中。我们注入 @FeignClient 接口时其实就是注入的这个代理类。 - 调用接口方法时,会被代理对象拦截,进入
ReflectiveFeign.FeignInvocationHandler
的invoke
方法执行请求。 - FeignInvocationHandler 会根据调用的接口方法获取已经构建好的方法处理器
SynchronousMethodHandler
,然后调用它的invoke
方法执行请求。 - 在 SynchronousMethodHandler 的 invoke 方法中,会先根据请求参数构建请求模板
RequestTemplate
,这个时候会处理参数中的占位符、拼接请求参数、处理body中的参数等等。 - 然后将 RequestTemplate 转成
Request
,在转换的过程中:- 先是用
RequestInterceptor
处理请求模板,因此我们可以自定义拦截器来定制化 RequestTemplate。 - 之后用
Target(HardCodedTarget)
处理请求地址,拼接上服务名前缀。 - 最后调用 RequestTemplate 的
request
方法获取到 Request 对象。
- 先是用
- 得到 Request 后,就调用 LoadBalancerFeignClient 的
execute
方法来执行请求并得到请求结果Response
:- 先构造 ClientRequest,并获取到负载均衡器
FeignLoadBalancer
,然后就执行负载均衡请求。 - 负载均衡请求最终进入到
AbstractLoadBalancerAwareClient,executeWithLoadBalancer
方法中,会先构建一个 LoadBalancerCommand,然后提交一个 ServerOperation。 - LoadBalancerCommand 会通过 LoadBalancerContext 根据服务名获取一个 Server。
- 在 ServerOperation 中根据 Server 的信息重构URI,将服务名替换为具体的IP地址,之后就可以发起真正的HTTP调用了。
- HTTP调用时,底层使用的组件默认是 HttpURLConnection;启用了okhttp,就是 okhttp 的 OkHttpClient;启用了 httpclient,就是 apache 的 HttpClient。
- 最红用 HTTP 客户端组件执行请求,得到响应结果
Response
。
- 先构造 ClientRequest,并获取到负载均衡器
- 得到 Response 后,就使用解码器
Decoder
解析响应结果,返回接口方法定义的返回类型。
负载均衡获取Server的核心组件是 LoadBalancerClient
,具体的源码分析可以参考 Ribbon 源码分析的两篇文章。LoadBalancerClient 负载均衡的原理可以看下面这张图。