OpenFeign与Ribbon源码分析总结与面试题
本篇内容:
- OpenFeign与Feign的关系
- Feign底层实现原理
- Ribbon是什么
- Ribbon底层实现原理
- Ribbon是如何实现失败重试的?
OpenFeign与Feign的关系
feign
是spring cloud
组件中的一个轻量级restful
的http
服务客户端,简化接口调用,将http
调用转为rpc
调用,让调用远程接口像调用同进程应用内的接口调用一样简单。
与dubbo
的rpc
远程调用一样,通过动态代理实现接口的调用。feign
通过封装包装请求体、发送http
请求、获取接口响应结果、序列化响应结果等接口调用动作来简化接口的调用。
openfeign
则是spring cloud
在feign
的基础上支持了spring mvc
的注解,如@RequesMapping
、@GetMapping
、@PostMapping
等。openfeign
还实现与Ribbon
的整合。
服务提供者只需要提供API
接口,而不需要像dubbo
那样需要强制使用implements
实现接口,使用fegin
不要求服务提供者在Controller
使用implements
关键字实现接口。
Feign底层实现原理
openfeign
通过包扫描将所有被@FeignClient
注解注释的接口扫描出来,并为每个接口注册一个FeignClientFactoryBean<T>
实例。FeignClientFactoryBean<T>
是一个FactoryBean<T>
,当Spring
调用FeignClientFactoryBean<T>
的getObject
方法时,openfeign
返回一个Feign
生成的动态代理类,拦截方法的执行。
feign
会为代理的接口的每个方法Method
都生成一个MethodHandler
。
当为接口上的@FeignClient
注解的url
属性配置服务提供者的url
时,其实就是不与Ribbon
整合,由SynchronousMethodHandler
实现接口方法远程同步调用,使用默认的Client
实现类Default
实例发起http
请求。
当接口上的@FeignClient
注解的url
属性不配置时,且会走负载均衡逻辑,也就是需要与Ribbon
整合使用。这时候不再是使用默认的Client
(Default
)调用接口,而是使用LoadBalancerFeignClient
调用接口(LoadBalancerFeignClient
也是Client
接口的实现类,最终还是使用Default
发起请求),由LoadBalancerFeignClient
实现与Ribbon
的整合。
Ribbon是什么
Ribbon
是Netflix
发布的开源项目,提供在服务消费端实现负载均衡调用服务提供者,从注册中心读取所有可用的服务提供者,在客户端每次调用接口时采用如轮询负载均衡算法选出一个服务提供者调用,因此,Ribbon
是一个客户端负载均衡器。
Ribbon
提供多种负载均衡算法的实现、提供重试支持。Feign
也提供重试支持,在SynchronousMethodHandler
的invoke
方法中实现,但Feign
的重试比较简单,只是向同一个服务节点发送请求,而Ribbon
的失败重试是重新选择一个服务节点调用的,在服务提供者部署多个节点的情况下,显然Feign
的重试机制是没有多大意义的。
Ribbon底层实现原理
下图是我在google
搜出的一道面试题,你们觉得这个答案正确吗?
Ribbon
与Fegin
整合的桥梁是FeignLoadBalancer
。
1、
Ribbon
会注册一个ILoadBalancer
(默认使用实现类ZoneAwareLoadBalancer
)负载均衡器,Feign
通过LoadBalancerFeignClient
调用FeignLoadBalancer
的executeWithLoadBalancer
方法来使用Ribbon
的ILoadBalancer
负载均衡器选择一个提供者节点发送http
请求,实际发送请求还是OpenFeign
的FeignLoadBalancer
发起的,Ribbon
从始至终都只负载负载均衡选出一个服务节点。2、
spring-cloud-netflix-ribbon
的自动配置类会注册一个RibbonLoadBalancerClient
,此RibbonLoadBalancerClient
正是Ribbon
为spring cloud
的负载均衡接口提供的实现类,用于实现@LoadBalancer
注解语意。
Ribbon
并非直接通过DiscoveryClient
从注册中心获取服务的可用提供者,而是通过ServerList<Server>
从注册中心获取服务提供者,ServerList
与DiscoveryClient
不一样,ServerList
不是Spring Cloud
定义的接口,而是Ribbon
定义的接口。以spring-cloud-kubernetes-ribbon
为例,spring-cloud-kubernetes-ribbon
为Ribbon
提供ServerList
的实现KubernetesServerList
。Ribbon
负责定时调用ServerList
的getUpdatedListOfServers
方法更新可用服务提供者。
怎么去获取可用的服务提供者节点由你自己去实现ServerList
接口,并将实现的ServerList
注册到Spring
容器。如果不提供ServerList
,那么使用的将是Ribbon
提供的默认实现类ConfigurationBasedServerList
,ConfigurationBasedServerList
并不会从注册中心读取获取服务节点,而是从配置文件中读取。
如果我们使用的注册中心是Eureka
,当我们在项目中添加spring-cloud-starter-netflix-eureka-client
时,其实就已经往项目中导入了一个ribbon-eureka
的jar
,由该jar
包提供Ribbon
与Eureka
整合所需的ServerList
:DiscoveryEnabledNIWSServerList
。
ribbon-eureka
源码地址:https://github.com/Netflix/ribbon/tree/master/ribbon-eureka
,感兴趣的朋友可以看下。
那么,现在你还会说Ribbon
是通过DiscoveryClient
从注册中心获取服务提供者的吗?当然,你也完全可以通过自己实现一个ServerList
,然后通过DiscoveryClient
从注册中心获取。
Ribbon是如何实现失败重试的?
Ribbon
提供RetryHandler
接口,并且默认使用DefaultLoadBalancerRetryHandler
。LoadBalancerCommand
的submit
方法中(在FeignLoadBalancer
的executeWithLoadBalancer
方法中调用),如果配置重试次数大于0
,则会调用RxJava
的API
支持重试。
public Observable<T> submit(final ServerOperation<T> operation){// .......
finalint maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
finalint maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
// Use the load balancer
Observable<T> o = (server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
@Override
public Observable<T> call(Server server){
//.......
// 调用相同节点的重试次数
if (maxRetrysSame > 0)
o = o.retry(retryPolicy(maxRetrysSame, true));
return o;
}
});
// 调用不同节点的重试次数
if (maxRetrysNext > 0 && server == null)
o = o.retry(retryPolicy(maxRetrysNext, false));
return o.onErrorResumeNext(...);
}
默认maxRetrysSame
(调用相同的重试次数)为0
,默认maxRetrysNext
(调用不同节点的重试次数)为1
。retryPolicy
方法是返回的是一个判断是否重试的决策者,由该决策者决定是否需要重试(抛出的异常是否允许重试,是否达到最大重试次数)。
private Func2<Integer, Throwable, Boolean> retryPolicy(finalint maxRetrys, finalboolean same){returnnew Func2<Integer, Throwable, Boolean>() {
@Override
public Boolean call(Integer tryCount, Throwable e){
if (e instanceof AbortExecutionException) {
returnfalse;
}
// 大于最大重试次数
if (tryCount > maxRetrys) {
returnfalse;
}
if (e.getCause() != null && e instanceof RuntimeException) {
e = e.getCause();
}
// 调用RetryHandler判断是否重试
return retryHandler.isRetriableException(e, same);
}
};
}
以上是 OpenFeign与Ribbon源码分析总结与面试题 的全部内容, 来源链接: utcz.com/a/27768.html