前言

书接上文,feign接口是如何注册到容器想必已然清楚,现在我们着重关心一个问题,feign调用服务的时候是如何抉择的?上一篇主要是从读源码的角度入手,后续将会逐步从软件构架方面进行剖析。

一、ReflectiveFeign.FeignInvocationHandler

从上文知道feign接口调用实质上是调用的对应的动态代理接口的InvocationHandler,跟踪源码发现默认的InvocationHandler实现就是FeignInvocationHandler。现在我们看一下这个FeignInvocationHandler.invoke(...)方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("equals".equals(method.getName())) {
try {
Object otherHandler =
args.length > 0 && args[0] != null ? Proxy.getInvocationHandler(args[0]) : null;
return equals(otherHandler);
} catch (IllegalArgumentException e) {
return false;
}
} else if ("hashCode".equals(method.getName())) {
return hashCode();
} else if ("toString".equals(method.getName())) {
return toString();
}
// dispath 是缓存的method 以及 method对应的MethodHandler
return dispatch.get(method).invoke(args);
}

从代码中可以看到他是直接从缓存中拿到对应的MethodHandler,然后调用的MethodHandler的invoke方法。我们看一下MethodHandler都有哪些实现:

可以看到就两个实现, DefaultMethodHandler处理的是feign接口中的Default修饰的方法。我们调用的远程接口用的是SynchronousMethodHandler实现。那么可以看到我们最终对feing接口的某个方法的调用实际上调用的是SynchronousMethodHandler.invoke(...)方法。跟踪代码发现,最终调用的是SynchronousMethodHandler持有的Client的实例的execute方法。那么我们看一下Client都有那些实现:

这里跟踪SynchronousMethodHandler的创建过程发现Client的创建是按照如下逻辑进行的(FeignClientFactoryBean.loadBalance):


protected <T> T loadBalance(Feign.Builder builder, FeignContext context,
HardCodedTarget<T> target) {
Client client = getOptional(context, Client.class);
if (client != null) {
builder.client(client);
Targeter targeter = get(context, Targeter.class);
return targeter.target(this, builder, context, target);
} throw new IllegalStateException(
"No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-netflix-ribbon?");
}

从上述代码可以看到,他从context中获取到client,然后通过client获取执行。想必feign肯定是自动装备了一个Client,我们看一下他的默认配置:

显然配置必定是从FeignAutoConfiguration 或者 FeignRibbonClientAutoConfiguration进行配置的,查看这两个类最终发现Client是通过FeignRibbonClientAutoConfiguration进行注入的(通过@Import引入的DefaultFeignLoadBalancedConfiguration进行注入):


@Configuration
class DefaultFeignLoadBalancedConfiguration { @Bean
@ConditionalOnMissingBean
public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,
SpringClientFactory clientFactory) {
return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,
clientFactory);
} }

所以我们调用feign接口的某一个方法,最终调用的LoadBalancerFeignClient.execute()方法。那么负载均衡相关逻辑应该是在此接入的。

二、LoadBalancerFeignClient 做了些什么

先看核心代码,注意注释部分:


public Response execute(Request request, Request.Options options) throws IOException {
try {
// URL 处理
URI asUri = URI.create(request.url());
String clientName = asUri.getHost();
URI uriWithoutHost = cleanUrl(request.url(), clientName);
FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
this.delegate, request, uriWithoutHost);
// 获取调用服务配置
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(clientName) 创建了一个负载均衡的客户端,它实际上就是生成的如下所述的类:


public class FeignLoadBalancer extends
AbstractLoadBalancerAwareClient<FeignLoadBalancer.RibbonRequest, FeignLoadBalancer.RibbonResponse>

熟悉ribbon的朋友应该知道AbstractLoadBalancerAwareClient 就是Ribbon负载均衡调用的父类。具体的负载均衡实现策略,下一章在详细描述。至此我们可以得出结论:feign集成负载均衡是通过将FeignLoadBalancer作为调用feign接口的实际执行者,从而达到负载均衡的效果。可以看到这里与Ribbon高度的解耦,相当于我们获取了服务名、调用地址、调用参数后,最终交由一个执行器去调用。执行器并不关心参数从何而来,这里基于Ribbon提供的执行器实现只是更具传递的服务名找到了一个正确的实例去调用而已。

三 、 小结

至此我们可以看到初步职责划分: 代理对象、请求与响应解析、执行器三个职能部门。

1) 代理对象职责是: 将feign接口中方法的调用转接到对FeignInvocationHandler的invoke调用,在invoke函数中通过方法名称找到对应的SynchronousMethodHandler。

2) 执行器: 负载根据请求与详情解析出的调用信息(调用服务名、调用地址、调用参数)发起调用,他不关心参数是如何来的

一个系统的好坏、可扩展性高低很大程序上取决于系统中各职能部门划分是否清晰以及各个职能部分的权限是否越界。

微服务通信之feign集成负载均衡的更多相关文章

  1. 微服务通信之feign的注册、发现过程

    前言 feign 是目前微服务间通信的主流方式,是springCloud中一个非常重要的组件.他涉及到了负载均衡.限流等组件.真正意义上掌握了feign可以说就掌握了微服务. 一.feign的使用 f ...

  2. Java微服务(二):负载均衡、序列化、熔断

    本文接着上一篇写的<Java微服务(二):服务消费者与提供者搭建>,上一篇文章主要讲述了消费者与服务者的搭建与简单的实现.其中重点需要注意配置文件中的几个坑. 本章节介绍一些零散的内容:服 ...

  3. springCloud搭建微服务集群+Zuul服务器端负载均衡

    概述 最近研究了一下springCloud的微服务集群,主要用到了SpringCloud的服务发现和服务器端负载均衡,所有的项目都是用的springboot,可以和springCloud无缝对接. 技 ...

  4. Spring Cloud微服务开发笔记5——Ribbon负载均衡策略规则定制

    上一篇文章单独介绍了Ribbon框架的使用,及其如何实现客户端对服务访问的负载均衡,但只是单独从Ribbon框架实现,没有涉及spring cloud.本文着力介绍Ribbon的负载均衡机制,下一篇文 ...

  5. 微服务Kong(十)——负载均衡参考

    KONG为请求多个后端服务提供了多种负载均衡方案:一种是简单的基于DNS,另一种是更加动态的环形均衡器,他在不需要DNS服务器的情况下也允许服务注册. 一.基于DNS的负载均衡 当使用基于DNS的负载 ...

  6. 微服务深入浅出(4)-- 负载均衡Ribbon

    Spring Cloud中可以使用RestTemplate+Ribbon的解决方案来将负载均衡以代码的形式封装到客户端中. 通过查阅官方文档可以知道,只需要在程序的IoC容器中注入一个restTemp ...

  7. 微服务通信之feign的配置隔离

    前言 由上文我们知道针对某一个Feign接口,我们可以给他设置特定的配置类.那如果现在有一个服务,我们只想对A服务配置一个拦截器拦截请求而不影响其他服务,那应该怎么做呢? 一.feign接口配置 由前 ...

  8. SpringCloud初体验:三、Feign 服务间调用(FeignClient)、负载均衡(Ribbon)、容错/降级处理(Hystrix)

    FeignOpenFeign Feign是一种声明式.模板化的HTTP客户端. 看了解释过后,可以理解为他是一种 客户端 配置实现的策略,它实现 服务间调用(FeignClient).负载均衡(Rib ...

  9. SpringCloud学习系列之二 ----- 服务消费者(Feign)和负载均衡(Ribbon)使用详解

    前言 本篇主要介绍的是SpringCloud中的服务消费者(Feign)和负载均衡(Ribbon)功能的实现以及使用Feign结合Ribbon实现负载均衡. SpringCloud Feign Fei ...

随机推荐

  1. SpringCloud Gateway高阶之Sentinel限流、熔断

    前言 为什么需要服务熔断和降级?微服务是当前业界的一大趋势,原理就是将单一职责的功能模块独立化为子服务,降低服务间的耦合,服务间互相调用.但是这样也会出现一些问题: 上图中大量微服务互相调用,存在大量 ...

  2. 11.QT-ffmpeg+QAudioOutput实现音频播放器

    1.前言      由于QAudioOutput支持的输入数据必须是原始数据,所以播放mp3,WAV,AAC等格式文件,需要解封装后才能支持播放.      而在QT中,提供了QMediaPlayer ...

  3. Spring Boot 集成阿里云 OSS 进行文件存储

    最近因为项目中需要存储很多的图片,不想存储到服务器上,因此就直接选用阿里云的对象服务(Object Storage Service,简称 OSS)来进行存储,本文将介绍 Spring Boot 集成 ...

  4. Unit5:广播

    静态广播 1.定义 public class TestBroadCast extends BroadcastReceiver { @Override public void onReceive(Con ...

  5. springboot中图标的定制

    因为我用的版本是org.springframework.boot spring-boot-starter-parent 2.3.3.RELEASE 第一种方法: 配置一个application.yml ...

  6. 高可用集群之keepalived+lvs实战2

    keepalived简介 lvs在我之前的博客<高负载集群实战之lvs负载均衡-技术流ken>中已经进行了详细的介绍和应用,在这里就不再赘述.这篇博文将把lvs与keepalived相结合 ...

  7. java 多线程-4

    十四.sleep方法和wait方法的区别 [面试题] 相同点: 一旦执行方法,都可以使得当前线程进入阻塞状态. 不同点: 两个方法的声明位置不同:Thread类声明sleep():Object类中声明 ...

  8. Mac新手必看教程——轻松玩转Mac OS

    背景: 大部分用户接触的第一个操作系统大多是windows,本人记得曾经小学的微机课也是以win98为基础学习了一众office软件.随着工作的多样化,单一的windows系统已经无法满足部分需求,而 ...

  9. Java多线程--原子性、可见性、有序性

    计算机的内存模型: 计算机在运行行程序的时候,指令由CPU执行,计算机上数据存放在物理内存当中,CPU在执行指令的时候免不了要和数据打交道.刚开始,还相安无事的,但是随着CPU技术的发展,CPU的执行 ...

  10. 使用Navicat连接MySQL8.0版本报1251错误

    出现1251错误是因为,MySQL8.0版本改变了密码的验证规则caching_sha2_password,MySQL之前的版本验证规则是mysql_native_password,现在需要修改MyS ...