前言

前情回顾

上一讲我们已经知道了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实现负载均衡的原理分析的更多相关文章

  1. 【一起学源码-微服务】Eureka+Ribbon+Feign阶段性总结

    前言 想说的话 这里已经梳理完Eureka.Ribbon.Feign三大组件的基本原理了,今天做一个总结,里面会有一个比较详细的调用关系流程图. 说明 原创不易,如若转载 请标明来源! 博客地址:一枝 ...

  2. 阶段5 3.微服务项目【学成在线】_day09 课程预览 Eureka Feign_05-Feign远程调用-客户端负载均衡介绍

    2 Feign远程调用 在前后端分离架构中,服务层被拆分成了很多的微服务,服务与服务之间难免发生交互,比如:课程发布需要调用 CMS服务生成课程静态化页面,本节研究微服务远程调用所使用的技术. 下图是 ...

  3. 微服务框架——SpringCloud(三)

    1.Zuul服务网关 作用:路由转发和过滤,将请求转发到微服务或拦截请求.Zuul默认集成了负载均衡功能. 2.Zuul实现路由 a.新建springboot项目,依赖选择 Eureka Discov ...

  4. 【一起学源码-微服务】Feign 源码二:Feign动态代理构造过程

    前言 前情回顾 上一讲主要看了@EnableFeignClients中的registerBeanDefinitions()方法,这里面主要是 将EnableFeignClients注解对应的配置属性注 ...

  5. 【一起学源码-微服务】Ribbon源码五:Ribbon源码解读汇总篇~

    前言 想说的话 [一起学源码-微服务-Ribbon]专栏到这里就已经全部结束了,共更新四篇文章. Ribbon比较小巧,这里是直接 读的spring cloud 内嵌封装的版本,里面的各种config ...

  6. 【一起学源码-微服务】Nexflix Eureka 源码十:服务下线及实例摘除,一个client下线到底多久才会被其他实例感知?

    前言 前情回顾 上一讲我们讲了 client端向server端发送心跳检查,也是默认每30钟发送一次,server端接收后会更新注册表的一个时间戳属性,然后一次心跳(续约)也就完成了. 本讲目录 这一 ...

  7. 【一起学源码-微服务】Nexflix Eureka 源码十三:Eureka源码解读完结撒花篇~!

    前言 想说的话 [一起学源码-微服务-Netflix Eureka]专栏到这里就已经全部结束了. 实话实说,从最开始Eureka Server和Eureka Client初始化的流程还是一脸闷逼,到现 ...

  8. 【一起学源码-微服务】Hystrix 源码一:Hystrix基础原理与Demo搭建

    说明 原创不易,如若转载 请标明来源! 欢迎关注本人微信公众号:壹枝花算不算浪漫 更多内容也可查看本人博客:一枝花算不算浪漫 前言 前情回顾 上一个系列文章讲解了Feign的源码,主要是Feign动态 ...

  9. 【一起学源码-微服务】Ribbon 源码二:通过Debug找出Ribbon初始化流程及ILoadBalancer原理分析

    前言 前情回顾 上一讲讲了Ribbon的基础知识,通过一个简单的demo看了下Ribbon的负载均衡,我们在RestTemplate上加了@LoadBalanced注解后,就能够自动的负载均衡了. 本 ...

随机推荐

  1. 解决margin塌陷和margin合并

    <!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...

  2. jq杂项方法/工具方法----trim() html() val() text() attr()

    https://www.cnblogs.com/sandraryan/ $.trim() 函数用于去除字符串两端的空白字符.在中间的时候不会去掉. var str = ' 去除字符串左右两端的空格,换 ...

  3. C#的选择语句练习(一)

    1.请输入一个数x,若x<1,则y=x:若1<=x<10,则y=2x-1:若x>=10,则y=3x-11,要求随意输入一个x值,求出y值. 2.输入问题[你有房子吗?],若回答 ...

  4. 2018-10-19-Nuget-通过-dotnet-命令行发布

    title author date CreateTime categories Nuget 通过 dotnet 命令行发布 lindexi 2018-10-19 09:15:53 +0800 2018 ...

  5. Python--day39--管道和数据共享(面试可能会问到)

    1,管道 上面所述挂起即为阻塞 管道.py from multiprocessing import Pipe, Process def func(conn1,conn2): conn2.close() ...

  6. python基础六之编码

    python中编码的特点: 1,各个编码之间的二进制是不能互相识别的,会产生乱码 2,文件的储存和传输是不能用Unicode的 python3的编码 在python3中字符串在内存中是用Unicode ...

  7. java 字节→字符转换流

    OutputStreamWriter:把字节输出流对象转成字符输出流对象 InputStreamReader:把字节输入流对象转成字符输入流对象 FileWriter和FileReader分别是Out ...

  8. C# json 转 xml 字符串

    本文告诉大家如何将 json 转 xml 或将 xml 转 json 字符串 首先需要安装 Newtonsoft.Json 库,打开 VisualStudio 2019 新建一个 dotnet cor ...

  9. H3C IPv6地址分类

  10. Linux 内核 低级 sysfs 操作

    kobject 是在 sysfs 虚拟文件系统之后的机制. 对每个在 sysfs 中发现的目录, 有一个 kobject 潜伏在内核某处. 每个感兴趣的 kobject 也输出一个或多个属性, 它出现 ...