Feign注重服务端的负载均衡,比如后端A服务调B服务,若B服务有多个部署实例,A在用Feign调用时实际是有负载均衡的。
如果用一句话来概括Feign的工作原理,那就是:
在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创建远程接口的本地JDK Proxy代理实例。然后,将这些本地Proxy代理实例,注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。
比如:
@FeignClient(value = "seckill-provider", path = "/api/demo/")
public interface DemoFeignClient {
@GetMapping("/hello/v1")
Result<JSONObject> hello();
@RequestMapping(value = "/echo/{word}/v1", method = RequestMethod.GET)
Result<JSONObject> echo(
@PathVariable(value = "word") String word);
}
在DemoFeignClient 接口上,加有@FeignClient 注解。也即是说,Feign在启动时,会为其创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。
一、远程接口的本地JDK Proxy代理实例
远程接口的本地JDK Proxy代理实例,有以下特点:
- (1)本地Proxy代理实例,实现了一个加 @FeignClient 注解的远程调用接口;
- (2)本地Proxy代理实例,能在内部进行HTTP请求的封装,以及发送HTTP 请求;
- (3)本地Proxy代理实例,能处理远程HTTP请求的响应,并且完成结果的解码,然后返回给调用者
二、本地JDK Proxy代理实例的创建过程
2.1 FeignInvocationHandler
通过 JDK Proxy 生成动态代理类,核心步骤就是需要定制一个调用处理器,具体来说,就是实现JDK中位于java.lang.reflect 包中的 InvocationHandler 调用处理器接口,并且实现该接口的 invoke(…) 抽象方法。
为了创建Feign的远程接口的代理实现类,Feign提供了自己的一个默认的调用处理器,叫做 FeignInvocationHandler 类。
调用处理器可以进行替换,如果Feign与Hystrix结合使用,则会替换成 HystrixInvocationHandler 调用处理器类
默认的调用处理器 FeignInvocationHandler 有一个非常重要Map类型成员 dispatch 映射,保存着远程接口方法到MethodHandler方法处理器的映射,即 dispatch 中保存了所有的方法以及这个方法对应的方法处理器。
FeignInvocationHandler 源码如下:
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);
}
//默认Feign调用的处理
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//...
//首先,根据方法实例,从dispatch(方法实例对象和方法处理器的映射)中,
//取得 方法处理器,然后,调用 方法处理器 的 invoke(...) 方法
return dispatch.get(method).invoke(args);
}
}
重点在于invoke(…)方法,虽然核心代码只有一行,但是其功能是复杂的:
(1)根据Java反射的方法实例,在dispatch 映射对象中,找到对应的MethodHandler 方法处理器;
(2)调用MethodHandler方法处理器的 invoke(...) 方法,完成实际的HTTP请求和结果的处理。
2.2 MethodHandler
Feign的方法处理器 MethodHandler 是一个独立的接口,定义在 InvocationHandlerFactory 接口中,仅仅拥有一个invoke(…)方法,源码如下:
//定义在InvocationHandlerFactory接口中
public interface InvocationHandlerFactory {
//方法处理器接口,仅仅拥有一个invoke(…)方法
interface MethodHandler {
//完成远程URL请求
Object invoke(Object[] argv) throws Throwable;
}
}
MethodHandler 的invoke(…)方法,主要职责是完成实际远程URL请求,然后返回解码后的远程URL的响应结果。Feign提供了默认的 SynchronousMethodHandler 实现类,提供了基本的远程URL的同步请求处理。
SynchronousMethodHandler
final class SynchronousMethodHandler implements MethodHandler {
//…
// 执行Handler 的处理
public Object invoke(Object[] argv) throws Throwable {
RequestTemplate requestTemplate = this.buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while(true) {
try {
return this.executeAndDecode(requestTemplate);
} catch (RetryableException var5) {
//…省略不相干代码
}
}
}
//执行请求,然后解码结果
Object executeAndDecode(RequestTemplate template) throws Throwable {
Request request = this.targetRequest(template);
long start = System.nanoTime();
Response response;
try {
response = this.client.execute(request, this.options);
response.toBuilder().request(request).build();
}
}
}
SynchronousMethodHandler的invoke(…)方法,调用了自己的executeAndDecode(…) 请求执行和结果解码方法。该方法的工作步骤:
- (1)首先通 RequestTemplate 请求模板实例,生成远程URL请求实例 request;
- (2)然后用自己的 feign 客户端client成员,excecute(…) 执行请求,并且获取 response 响应;
- (3)对response 响应进行结果解码。
2.3 Feign 客户端组件 feign.Client
负责端到端的执行URL请求。其核心的逻辑:发送request请求到服务器,并接收response响应后进行解码。
feign.Client 类,是代表客户端的顶层接口,只有一个抽象方法,源码如下:
public interface Client {
//提交HTTP请求,并且接收response响应后进行解码
Response execute(Request request, Options options) throws IOException;
}
不同的feign.Client 实现类,内部完成HTTP请求的组件和技术不同,feign.Client 有多个不同的实现:
(1)Client.Default类:默认的feign.Client 客户端实现类,内部使用HttpURLConnnection 完成URL请求处理;
(2)ApacheHttpClient 类:内部使用 Apache httpclient 开源组件完成URL请求处理的feign.Client 客户端实现类;
(3)OkHttpClient类:内部使用 OkHttp3 开源组件完成URL请求处理的feign.Client 客户端实现类;
(4)LoadBalancerFeignClient 类:内部使用 Ribben 负载均衡技术完成URL请求处理的feign.Client 客户端实现类。
三、整体的远程调用执行流程
第1步:通过Spring IOC 容器实例,装配代理实例,然后进行远程调用。
Feign在启动时,会为加上了@FeignClient注解的所有远程接口(包括 DemoClient 接口),创建一个本地JDK Proxy代理实例,并注册到Spring IOC容器。在这里,暂且将这个Proxy代理实例,叫做 DemoClientProxy,稍后,会详细介绍这个Proxy代理实例的具体创建过程。
第2步:执行 InvokeHandler 调用处理器的invoke(…)方法
JDK Proxy动态代理实例的真正的方法调用过程,具体是通过 InvokeHandler 调用处理器完成的。故,这里的DemoClientProxy代理实例,会调用到默认的FeignInvocationHandler 调用处理器实例的invoke(…)方法。
默认的调用处理器 FeignInvocationHandler,内部保持了一个远程调用方法实例和方法处理器的一个Key-Value键值对Map映射dispatch 。FeignInvocationHandler 在其invoke(…)方法中,会根据Java反射的方法实例,在dispatch 映射对象中,找到对应的 MethodHandler 方法处理器,然后由后者完成实际的HTTP请求和结果的处理。
所以在第2步中,FeignInvocationHandler 会从自己的 dispatch映射中,找到hello()方法所对应的MethodHandler 方法处理器,然后调用其 invoke(…)方法。
第3步:执行 MethodHandler 方法处理器的invoke(…)方法
通过前面关于 MethodHandler 方法处理器的非常详细的组件介绍,feign默认的方法处理器为 SynchronousMethodHandler,其invoke(…)方法主要是通过内部成员feign客户端成员 client,完成远程 URL 请求执行和获取远程结果。feign.Client 客户端有多种类型,不同的类型,完成URL请求处理的具体方式不同。
第4步:通过 feign.Client 客户端成员,完成远程 URL 请求执行和获取远程结果
如果MethodHandler方法处理器实例中的client客户端,是默认的 feign.Client.Default 实现类性,则使用JDK自带的HttpURLConnnection类,完成远程 URL 请求执行和获取远程结果。
如果MethodHandler方法处理器实例中的client客户端,是 ApacheHttpClient 客户端实现类性,则使用 Apache httpclient 开源组件,完成远程 URL 请求执行和获取远程结果。
注意:
SynchronousMethodHandler 并不是直接完成远程URL的请求,而是通过负载均衡机制,定位到合适的远程server 服务器,然后再完成真正的远程URL请求。换句话说,SynchronousMethodHandler实例的client成员,其实际不是feign.Client.Default类型,而是 LoadBalancerFeignClient 客户端负载均衡类型。
因此,上面的第3步,如果进一步细分话,大致如下:(1)首先通过 SynchronousMethodHandler 内部的client实例,实质为负责客户端负载均衡 LoadBalancerFeignClient 实例,首先查找到远程的 server 服务端;(2) 然后再由LoadBalancerFeignClient 实例内部包装的feign.Client.Default 内部类实例,去请求server端服务器,完成URL请求处理。