【一起学源码-微服务】Feign 源码三:Feign结合Ribbon实现负载均衡的原理分析
前言
前情回顾
上一讲我们已经知道了Feign的工作原理其实是在项目启动的时候,通过JDK动态代理为每个FeignClinent生成一个动态代理。
动态代理的数据结构是:ReflectiveFeign.FeignInvocationHandler。其中包含target(里面是serviceName等信息)和dispatcher(map数据结构,key是请求的方法名,方法参数等,value是SynchronousMethodHandler)。
如下图所示:

本讲目录
这一讲主要是Feign与Ribbon结合实现负载均衡的原理分析。
说明
原创不易,如若转载 请标明来源!
博客地址:一枝花算不算浪漫
微信公众号:壹枝花算不算浪漫
源码分析
Feign结合Ribbon实现负载均衡原理
通过前面的分析,我们可以直接来看下SynchronousMethodHandler中的代码:
final class SynchronousMethodHandler implements MethodHandler {
@Override
public Object invoke(Object[] argv) throws Throwable {
// 生成请求类似于:GET /sayHello/wangmeng HTTP/1.1
RequestTemplate template = buildTemplateFromArgs.create(argv);
Retryer retryer = this.retryer.clone();
while (true) {
try {
return executeAndDecode(template);
} catch (RetryableException e) {
retryer.continueOrPropagate(e);
if (logLevel != Logger.Level.NONE) {
logger.logRetry(metadata.configKey(), logLevel);
}
continue;
}
}
}
Object executeAndDecode(RequestTemplate template) throws Throwable {
// 构建request对象:GET http://serviceA/sayHello/wangmeng HTTP/1.1
Request request = targetRequest(template);
if (logLevel != Logger.Level.NONE) {
logger.logRequest(metadata.configKey(), logLevel, request);
}
Response response;
long start = System.nanoTime();
try {
// 这个client就是之前构建的LoadBalancerFeignClient,options是超时时间
response = client.execute(request, options);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));
}
throw errorExecuting(request, e);
}
long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
// 下面逻辑都是构建返回值response
boolean shouldClose = true;
try {
if (logLevel != Logger.Level.NONE) {
response =
logger.logAndRebufferResponse(metadata.configKey(), logLevel, response, elapsedTime);
// ensure the request is set. TODO: remove in Feign 10
response.toBuilder().request(request).build();
}
if (Response.class == metadata.returnType()) {
if (response.body() == null) {
return response;
}
if (response.body().length() == null ||
response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {
shouldClose = false;
return response;
}
// Ensure the response body is disconnected
byte[] bodyData = Util.toByteArray(response.body().asInputStream());
return response.toBuilder().body(bodyData).build();
}
if (response.status() >= 200 && response.status() < 300) {
if (void.class == metadata.returnType()) {
return null;
} else {
return decode(response);
}
} else if (decode404 && response.status() == 404 && void.class != metadata.returnType()) {
return decode(response);
} else {
throw errorDecoder.decode(metadata.configKey(), response);
}
} catch (IOException e) {
if (logLevel != Logger.Level.NONE) {
logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime);
}
throw errorReading(request, response, e);
} finally {
if (shouldClose) {
ensureClosed(response.body());
}
}
}
}
这里主要是构建request数据,然后通过request和options去通过LoadBalancerFeignClient.execute()方法去获得返回值。我们可以接着看client端的调用:
public class LoadBalancerFeignClient implements Client {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
try {
// asUri: http://serviceA/sayHello/wangmeng
URI asUri = URI.create(request.url());
// clientName:serviceA
String clientName = asUri.getHost();
// uriWithoutHost: http://sayHello/wangmeng
URI uriWithoutHost = cleanUrl(request.url(), clientName);
// 这里ribbonRequest:GET http:///sayHello/wangmeng HTTP/1.1
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 这里面config只有两个超时时间,一个是connectTimeout:5000,一个是readTimeout:5000
IClientConfig requestConfig = getClientConfig(options, clientName);
// 真正执行负载均衡的地方
return lbClient(clientName).executeWithLoadBalancer(ribbonRequest,
requestConfig).toResponse();
}
catch (ClientException e) {
IOException io = findIOException(e);
if (io != null) {
throw io;
}
throw new RuntimeException(e);
}
}
}
接着我们看下lbClient()和executeWithLoadBalancer()
public class LoadBalancerFeignClient implements Client {
private FeignLoadBalancer lbClient(String clientName) {
return this.lbClientFactory.create(clientName);
}
}
public class CachingSpringLoadBalancerFactory {
public FeignLoadBalancer create(String clientName) {
if (this.cache.containsKey(clientName)) {
return this.cache.get(clientName);
}
IClientConfig config = this.factory.getClientConfig(clientName);
// 获取Ribbon ILoadBalancer信息
ILoadBalancer lb = this.factory.getLoadBalancer(clientName);
ServerIntrospector serverIntrospector = this.factory.getInstance(clientName, ServerIntrospector.class);
FeignLoadBalancer client = enableRetry ? new RetryableFeignLoadBalancer(lb, config, serverIntrospector,
loadBalancedRetryPolicyFactory, loadBalancedBackOffPolicyFactory, loadBalancedRetryListenerFactory) : new FeignLoadBalancer(lb, config, serverIntrospector);
this.cache.put(clientName, client);
return client;
}
}
这里是获取了ILoadBalancer数据,里面包含了Ribbon获取的serviceA所有服务节点信息。

这里已经获取到ILoadBalancer,里面包含serviceA服务器所有节点请求host信息。接下来就是从中负载均衡选择一个节点信息host出来。
public abstract class AbstractLoadBalancerAwareClient<S extends ClientRequest, T extends IResponse> extends LoadBalancerContext implements IClient<S, T>, IClientConfigAware {
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) {
URI finalUri = reconstructURIWithServer(server, request.getUri());
S requestForServer = (S) request.replaceUri(finalUri);
try {
return Observable.just(AbstractLoadBalancerAwareClient.this.execute(requestForServer, requestConfig));
}
catch (Exception e) {
return Observable.error(e);
}
}
})
.toBlocking()
.single();
} catch (Exception e) {
Throwable t = e.getCause();
if (t instanceof ClientException) {
throw (ClientException) t;
} else {
throw new ClientException(e);
}
}
}
}
public class LoadBalancerCommand<T> {
public Observable<T> submit(final ServerOperation<T> operation) {
final ExecutionInfoContext context = new ExecutionInfoContext();
if (listenerInvoker != null) {
try {
listenerInvoker.onExecutionStart();
} catch (AbortExecutionException e) {
return Observable.error(e);
}
}
final int maxRetrysSame = retryHandler.getMaxRetriesOnSameServer();
final int maxRetrysNext = retryHandler.getMaxRetriesOnNextServer();
// Use the load balancer
Observable<T> o =
(server == null ? selectServer() : Observable.just(server))
.concatMap(new Func1<Server, Observable<T>>() {
}
// 省略代码...
// selectServer是真正执行负载均衡的逻辑
private Observable<Server> selectServer() {
return Observable.create(new OnSubscribe<Server>() {
@Override
public void call(Subscriber<? super Server> next) {
try {
// loadBalancerURI是http:///sayHello/wangmeng, loadBalancerKey为null
Server server = loadBalancerContext.getServerFromLoadBalancer(loadBalancerURI, loadBalancerKey);
next.onNext(server);
next.onCompleted();
} catch (Exception e) {
next.onError(e);
}
}
});
}
}
public class LoadBalancerContext implements IClientConfigAware {
public Server getServerFromLoadBalancer(@Nullable URI original, @Nullable Object loadBalancerKey) throws ClientException {
String host = null;
int port = -1;
if (original != null) {
host = original.getHost();
}
if (original != null) {
Pair<String, Integer> schemeAndPort = deriveSchemeAndPortFromPartialUri(original);
port = schemeAndPort.second();
}
// 获取到ILoadBalancer,这里面有IRule的信息及服务节点所有信息
ILoadBalancer lb = getLoadBalancer();
if (host == null) {
// Partial URI or no URI Case
// well we have to just get the right instances from lb - or we fall back
if (lb != null){
// 这里就执行真正的chooseServer的逻辑了。默认的rule为ZoneAvoidanceZule
Server svc = lb.chooseServer(loadBalancerKey);
if (svc == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Load balancer does not have available server for client: "
+ clientName);
}
host = svc.getHost();
if (host == null){
throw new ClientException(ClientException.ErrorType.GENERAL,
"Invalid Server for :" + svc);
}
logger.debug("{} using LB returned Server: {} for request {}", new Object[]{clientName, svc, original});
return svc;
}
// 省略代码
}
}
}
上面代码已经很清晰了,这里就是真正的通过ribbon的 rule.chooseServer()负载均衡地选择了一个服务节点调用,debug如下:

到了这里feign与ribbon的分析也就结束了,返回请求url信息,然后得到response结果:

总结
上面已经分析了Feign与Ribbon的整合,最终还是落到Ribbon中的ILoadBalancer中,使用最后使用IRule去选择对应的server数据。
下一讲 会画一个很大的图,包含Feign、Ribbon、Eureka关联的图,里面会画出每个组件的细节及依赖关系。也算是学习至今的一个总复习了。
申明
本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!
感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫

【一起学源码-微服务】Feign 源码三:Feign结合Ribbon实现负载均衡的原理分析的更多相关文章
- 【一起学源码-微服务】Eureka+Ribbon+Feign阶段性总结
前言 想说的话 这里已经梳理完Eureka.Ribbon.Feign三大组件的基本原理了,今天做一个总结,里面会有一个比较详细的调用关系流程图. 说明 原创不易,如若转载 请标明来源! 博客地址:一枝 ...
- 阶段5 3.微服务项目【学成在线】_day09 课程预览 Eureka Feign_05-Feign远程调用-客户端负载均衡介绍
2 Feign远程调用 在前后端分离架构中,服务层被拆分成了很多的微服务,服务与服务之间难免发生交互,比如:课程发布需要调用 CMS服务生成课程静态化页面,本节研究微服务远程调用所使用的技术. 下图是 ...
- 微服务框架——SpringCloud(三)
1.Zuul服务网关 作用:路由转发和过滤,将请求转发到微服务或拦截请求.Zuul默认集成了负载均衡功能. 2.Zuul实现路由 a.新建springboot项目,依赖选择 Eureka Discov ...
- 【一起学源码-微服务】Feign 源码二:Feign动态代理构造过程
前言 前情回顾 上一讲主要看了@EnableFeignClients中的registerBeanDefinitions()方法,这里面主要是 将EnableFeignClients注解对应的配置属性注 ...
- 【一起学源码-微服务】Ribbon源码五:Ribbon源码解读汇总篇~
前言 想说的话 [一起学源码-微服务-Ribbon]专栏到这里就已经全部结束了,共更新四篇文章. Ribbon比较小巧,这里是直接 读的spring cloud 内嵌封装的版本,里面的各种config ...
- 【一起学源码-微服务】Nexflix Eureka 源码十:服务下线及实例摘除,一个client下线到底多久才会被其他实例感知?
前言 前情回顾 上一讲我们讲了 client端向server端发送心跳检查,也是默认每30钟发送一次,server端接收后会更新注册表的一个时间戳属性,然后一次心跳(续约)也就完成了. 本讲目录 这一 ...
- 【一起学源码-微服务】Nexflix Eureka 源码十三:Eureka源码解读完结撒花篇~!
前言 想说的话 [一起学源码-微服务-Netflix Eureka]专栏到这里就已经全部结束了. 实话实说,从最开始Eureka Server和Eureka Client初始化的流程还是一脸闷逼,到现 ...
- 【一起学源码-微服务】Hystrix 源码一:Hystrix基础原理与Demo搭建
说明 原创不易,如若转载 请标明来源! 欢迎关注本人微信公众号:壹枝花算不算浪漫 更多内容也可查看本人博客:一枝花算不算浪漫 前言 前情回顾 上一个系列文章讲解了Feign的源码,主要是Feign动态 ...
- 【一起学源码-微服务】Ribbon 源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析
前言 前情回顾 上一讲讲了Ribbon的基础知识,通过一个简单的demo看了下Ribbon的负载均衡,我们在RestTemplate上加了@LoadBalanced注解后,就能够自动的负载均衡了. 本 ...
随机推荐
- 【codeforces 520A】Pangram
[题目链接]:http://codeforces.com/problemset/problem/520/A [题意] 给你一个字符串. 统计里面有没有出现所有的英文字母->'a'..'z' 每个 ...
- 禁用GPU版本TensorFlow,切换到CPU版本TensorFlow。
#禁用gpu版本TensorFlow,因为CUDA号码从0开始,这里直接让CUDA使用-1的GPU,自然就无法使用gpu了. 代码前面加入: import osos.environ["CUD ...
- 2019-10-30-C#-dotnet-core-局域网组播方法
title author date CreateTime categories C# dotnet core 局域网组播方法 lindexi 2019-10-30 9:0:48 +0800 2019- ...
- iptables一个包过滤防火墙实例
环境:redhat9 加载了string time等模块 eth0 接外网──ppp0 eth1 接内网──192.168.0.0/24 #!/bin/sh # modprobe ipt_MASQUE ...
- 31页PPT:基于Spark的移动大数据挖掘
31页PPT:基于Spark的移动大数据挖掘 数盟11.16 Data Science Meetup(DSM北京)分享:基于Spark的移动大数据挖掘分享嘉宾:张夏天(TalkingData首席数据科 ...
- pytorch实现BiLSTM+CRF用于NER(命名实体识别)
pytorch实现BiLSTM+CRF用于NER(命名实体识别)在写这篇博客之前,我看了网上关于pytorch,BiLstm+CRF的实现,都是一个版本(对pytorch教程的翻译), 翻译得一点质量 ...
- 洛谷P2719 搞笑世界杯 题解 概率DP入门
作者:zifeiy 标签:概率DP 题目链接:https://www.luogu.org/problem/P2719 我们设 f[n][m] 用于表示还剩下n张A类票m张B类票时最后两张票相同的概率, ...
- Vue之webpack的安装与配置及其简单应用
一.文件结构 二.index.html <!DOCTYPE html> <html lang="en"> <head> <meta cha ...
- Spring Boot Thymeleaf 使用详解
在上篇文章Spring Boot (二):Web 综合开发中简单介绍了一下 Thymeleaf,这篇文章将更加全面详细的介绍 Thymeleaf 的使用.Thymeleaf 是新一代的模板引擎,在 S ...
- H3C 显示OSPF路由信息