什么是Feign
Feign是Netflix公司开发的声明式、模板化的的HTTP客户端工具,可以帮助我们更加优雅的调用HTTP API,同时也使得微服务之间的调用变得简单。
SpringCloud feign进行了增强,使得Feign更够支持SpringMvc注解
Feign的作用
- 作为HTTP的客户端替代RestTemplate,支持注解的方式
- Feign组件中引入了Ribbon和Hystrix组件,这使得Feign也能够为我们提供负载均衡、熔断、降级的功能;
我下面的贴图来自GitHub:openFeign
Feign的原理
先来看一下feign流程
在微服务启动时,Feign会进行包扫描,对加@FeignClient注解的接口,按照注解的规则,创建远程接口的本地JDK Proxy代理实例。然后,将这些本地Proxy代理实例,注入到Spring IOC容器中。当远程接口的方法被调用,由Proxy代理实例去完成真正的远程访问,并且返回结果。
下面跟着代码我们来一起看一下:
1.这里重点讲一下LoadBalancerFeignClient
public Response execute(Request request, Options options) throws IOException {
try {
//获取本次请求的URI
URI asUri = URI.create(request.url());
//获取服务的名称,FeignClient中定义的服务的名称
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
//存储URI
RibbonRequest ribbonRequest = new RibbonRequest(this.delegate, request, uriWithoutHost);
//存储connectTimeout、readTimeout的时间等
IClientConfig requestConfig = this.getClientConfig(options, clientName);
/*接下来的处理,会分成两步
*1、this.lbClient(clientName) //这里主要是通过CachingSpringLoadBalancerFactory获取FeignLoadBalancer
* 下面2步骤会分析如何获取的
*2、executeWithLoadBalancer(ribbonRequest, requestConfig)//这里就是通过LoadBalancer先选出需要调用的server,然后发送请求;
* 具体是先通过LoadBalancerCommand获取到要调用的server信息
* 然后再通过httpclient发送请求;
* 从LazyTracingFeignClient
* 下面3步骤会分析如何获取的
*
*/
return ((RibbonResponse)this.lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig)).toResponse();
} catch (ClientException var8) {
IOException io = this.findIOException(var8);
if (io != null) {
throw io;
} else {
throw new RuntimeException(var8);
}
}
}
}
2.CachingSpringLoadBalancerFactory#create(String clientName)
public FeignLoadBalancer create(String clientName) {
//private volatile Map<String, FeignLoadBalancer> cache = new ConcurrentReferenceHashMap();
//cache是一个volatile修饰的Map,
FeignLoadBalancer client = (FeignLoadBalancer)this.cache.get(clientName);
if (client != null) {
return client;
} else {
//缓存中没有找到,这里先进行config初始化;
IClientConfig config = this.factory.getClientConfig(clientName);
//从SpringClientFactory中获取ZoneAwareLoadBalancer
//ZoneAwareLoadBalancer是ribbon中一个非常重要的类,它用来获取eureka中的server列表,并且进行服务列表的更新;
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = (ServerIntrospector)this.factory.getInstance(clientName, ServerIntrospector.class);
//创建FeignLoadBalancer实例
FeignLoadBalancer client = this.loadBalancedRetryFactory != null ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector, this.loadBalancedRetryFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
//将该FeignLoadBalancer放入容器中
this.cache.put(clientName, client);
return (FeignLoadBalancer)client;
}
3.选取server、并进行调用
- AbstractLoadBalancerAwareClient#executeWithLoadBalancer()
- LoadBalancerCommand.submit()先通过ZoneAwareLoadBalancer获取到server信息,再执行调用
public T executeWithLoadBalancer(final S request, final IClientConfig requestConfig) throws ClientException {
LoadBalancerCommand command = this.buildLoadBalancerCommand(request, requestConfig);
try {
return (IResponse)command.submit(new ServerOperation<T>() {
//submit函数中获取到了server信息,并在回调函数中进行真正的服务请求
public Observable<T> call(Server server) {
URI finalUri = AbstractLoadBalancerAwareClient.this.reconstructURIWithServer(server, request.getUri());
ClientRequest requestForServer = request.replaceUri(finalUri);
try {
//这里会调用FeignLoadBalancer#FeignLoadBalancer.RibbonResponse execute(...)方法
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
} catch (Exception var5) {
return Observable.error(var5);
}
}
}).toBlocking().single();
} catch (Exception var6) {
......
}
}
4.FeignLoadBalancer#FeignLoadBalancer.RibbonResponse execute(…)
public FeignLoadBalancer.RibbonResponse execute(FeignLoadBalancer.RibbonRequest request, IClientConfig configOverride) throws IOException {
Options options;
if (configOverride != null) {
//这里会覆盖RibbonProperties中的信息
RibbonProperties override = RibbonProperties.from(configOverride);
options = new Options(override.connectTimeout(this.connectTimeout), override.readTimeout(this.readTimeout));
} else {
options = new Options(this.connectTimeout, this.readTimeout);
}
/*下面会通过client进行查询,由于我的项目中引入了zipkin,所以这里会调用org.springframework.cloud.sleuth.instrument.web.client.feign.LazyTracingFeignClient
* ==>LazyTracingFeignClient.execute(Request request, Options options)
* ==>TracingFeignClient.execute(Request request, Options options)
* ==>Client.Default.execute(Request request, Options options)
*/
Response response = request.client().execute(request.toRequest(), options);
return new FeignLoadBalancer.RibbonResponse(request.getUri(), response);
}
推荐阅读:
springCloud面试之feign+ribbon+hystirx交互概览
更多推荐
springCloud面试之Feign
发布评论