栏目分类

热点资讯

你的位置:欧冠体育下载首页注册 > 定制衣服产品 >

深入理解 OpenFeign 的架构道理

发布日期:2022-08-07 05:48    点击次数:74

 巨匠好,我是悟空呀。

上次我们深入解说了 Ribbon 的架构道理,这次我们再来看下 Feign 近程调用的架构道理。

1、理解近程调用

近程调用怎么理解呢?

近程调用和外埠调用是相对的,那我们先说外埠调用更好理解些,外埠调用就是同一个 Service 内里的编制 A 调用编制 B。

那近程调用就是差别 Service 之间的编制调用。Service 级的编制调用,就是我们自身布局要求 URL和要求参数,就能发起近程调用了。

在服务之间调用的话,我们都是基于 HTTP 和谈,普通用到的近程服务框架有 OKHttp3,Netty, HttpURLConnection 等。其调用流程以下:

然则这类虚线方框中的布局要求的进程是很繁缛的,有无更精练的编制呢?

Feign 就是来简化我们发起近程调用的代码的,那简化到什么程度呢?简化成就像调用外埠方式那样俭朴。

比喻我的开源名目 PassJava 中的运用 Feign 执行近程调用的代码:

//近程调用拿到该用户的深造时长 R memberStudyTimeList = studyTimeFeignService.getMemberStudyTimeListTest(id); 

而 Feign 又是 Spring Cloud 微服务技能栈中极度首要的一个组件,假定让你来策画这个微服务组件,你会怎么来策画呢?

我们需求推敲这几个要素:

怎么使近程调用像外埠方式调用俭朴? Feign 怎么找到近程服务的地点的? Feign 是怎么举行负载均衡的?

接上去我们萦绕这些焦点成就来一起看下 Feign 的策画道理。

2、Feign 和 OpenFeign

OpenFeign 组件的前身是 Netflix Feign 名目,它开始是作为 Netflix OSS 名目标一部份,由 Netflix 公司开发。其后 Feign 名目被贡献给了开源构造,是以才有了我们来日诰日运用的 Spring Cloud OpenFeign 组件。

Feign 和 OpenFeign 有良多迥然不同的地方,差别的是 OpenFeign 支持 MVC 表明。

可以或许觉得 OpenFeign 为 Feign 的增强版。

俭朴总结下 OpenFeign 能用来做什么:

OpenFeign 是声名式的 HTTP 客户端,让近程调用更俭朴。 供应了HTTP要求的模板,编写俭朴的接口和拔出表明,就能定义好HTTP要求的参数、名目、地点等信息 整合了Ribbon(负载均衡组件)和 Hystix(服务熔断组件),不需求体现运用这两个组件 Spring Cloud Feign 在 Netflix Feign的根抵上扩张了对SpringMVC表明的支持 三、OpenFeign 怎么用?

OpenFeign 的运用也很俭朴,这里照旧用我的开源 SpringCloud 名目 PassJava 作为示例。

开源地点: https://github.com/Jackson0714/PassJava-Platform

爱好的小搭档来点个 Star 吧,冲 2K Star。

Member 服务近程调用 Study 服务的编制 memberStudyTime(),以下图所示。

第一步:Member 服务需求定义一个 OpenFeign 接口:

@FeignClient("passjava-study") public interface StudyTimeFeignService {     @RequestMapping("study/studytime/member/list/test/{id}")     public R getMemberStudyTimeListTest(@PathVariable("id") Long id); } 

我们可以或许看到这个 interface 上增加了表明@FeignClient,并且括号内里指定了服务名:passjava-study。体现声名这个接口用来近程调用 passjava-study服务。

第二步:Member 启动类上增加 @EnableFeignClients表明开启近程调用服务,且需求开启服务缔造。以下所示:

@EnableFeignClients(basePackages = "com.jackson0714.passjava.member.feign") @EnableDiscoveryClient 

第三步:Study 服务定义一个编制,其编制门路和 Member 服务中的接口 URL 地点分歧即可。

URL 地点:"study/studytime/member/list/test/{id}"

@RestController @RequestMapping("study/studytime") public class StudyTimeController {     @RequestMapping("/member/list/test/{id}")     public R memberStudyTimeTest(@PathVariable("id") Long id) {        ...      } } 

第四步:Member 服务的 POM 文件中引入 OpenFeign 组件。

<dependency>     <groupId>org.springframework.cloud</groupId>     <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency

第五步:引入 studyTimeFeignService,Member 服务近程调用 Study 服务即可。

Autowired private StudyTimeFeignService studyTimeFeignService;  studyTimeFeignService.getMemberStudyTimeListTest(id); 

经由过程上面的示例,我们晓得,加了 @FeignClient 表明的接口后,我们就能调用它定义的接口,尔后就能调用到近程服务了。

这里你是否有疑问:为何接口都没有完成,就能调用了?

OpenFeign 运用起来倒是俭朴,然则内里的道理可没有那末俭朴,OpenFeign 帮我们做了良多事变,接上去我们来看下 OpenFeign 的架构道理。

四、梳理 OpenFeign 的焦点流程

先看下 OpenFeign 的焦点流程图:

1、在 Spring 名目启动阶段,服务 A 的OpenFeign 框架会发起一个被动的扫包流程。 2、从指定的目录下扫描并加载全体被 @FeignClient 表明修饰的接口,尔后将这些接口转换成 Bean,同一交给 Spring 来打点。 三、痛处这些接口会颠末 MVC Contract 和谈剖析,将编制上的表明都剖析出来,放到 MethodMetadata 元数据中。 四、基于上面加载的每个 FeignClient 接口,会生成一个静态代理工具,指向了一个包孕对应编制的 MethodHandler 的 HashMap。MethodHandler 对元数占据引用纠葛。生成的静态代理工具会被增加到 Spring 容器中,并注入到对应的服务里。 五、服务 A 调用接口,操办发起近程调用。 6、从静态代理工具 Proxy 中找到一个 MethodHandler 实例,生成 Request,包孕有服务的要求 URL(不包孕服务的 IP)。 七、颠末负载均衡算法找到一个服务的 IP 地点,拼接出要求的 URL 8、服务 B 处理惩罚服务 A 发起的近程调用要求,执行业务逻辑后,前去照顾给服务 A。

针对上面的流程,我们再来看下每一步的策画道理。首先被动扫包是怎么扫的呢?

五、OpeFeign 包扫描道理

上面的 PassJava 示例代码中,奔忙及到了一个 OpenFeign 的表明:@EnableFeignClients。痛处字面意义可以或许晓得,可以或许表明是开启 OpenFeign 功用的。

包扫描的根抵流程以下:

(1)@EnableFeignClients 这个表明运用 Spring 框架的 Import 表明导入了 FeignClientsRegistrar 类,起头了 OpenFeign 组件的加载。PassJava 示例代码以下所示。

// 启动类加之这个表明 @EnableFeignClients(basePackages = "com.jackson0714.passjava.member.feign")  // EnableFeignClients 类还引入了 FeignClientsRegistrar 类 @Import(FeignClientsRegistrar.class) public @interface EnableFeignClients {   ... } 

(2)FeignClientsRegistrar 担当 Feign 接口的加载。

源码以下所示:

 

@Override public void registerBeanDefinitions(AnnotationMetadata metadata,       BeanDefinitionRegistry registry) {    // 注册设置    registerDefaultConfiguration(metadata, registry);    // 注册 FeignClient    registerFeignClients(metadata, registry); } 

 

(3)registerFeignClients 会扫描指定包。

焦点源码以下,调用 find 编制来查找指定门路 basePackage 的全体带有 @FeignClients 表明的带有 @FeignClient 表明的类、接口。

 

Set<BeanDefinition> candidateComponents = scanner       .findCandidateComponents(basePackage); 

 

(4)只留存带有 @FeignClient 的接口。

 

// 鉴定是不是带有表明的 Bean。 if (candidateComponent instanceof AnnotatedBeanDefinition) {   // 鉴定是不是接口    AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;    AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();   // @FeignClient 只能指定在接口上。    Assert.isTrue(annotationMetadata.isInterface(),          "@FeignClient can only be specified on an interface"); 

 

接上去我们再来看这些扫描到的接口是怎么注册到 Spring 中。

6、注册 FeignClient 到 Spring 的道理

照旧在 registerFeignClients 编制中,当 FeignClient 扫描完后,就要为这些 FeignClient 接口生成一个静态代理工具。

顺藤摸瓜,进到这个编制内里,可以或许看到这一段代码:

 

BeanDefinitionBuilder definition = BeanDefinitionBuilder       .genericBeanDefinition(FeignClientFactoryBean.class); 

 

焦点就是 FeignClientFactoryBean 类,痛处类的名字我们可以或许晓得这是一个工厂类,用来创立 FeignClient Bean 的。

我们最起头用的 @FeignClient,内里有个参数 "passjava-study",这个是表明的属性,当 OpenFeign 框架去创立 FeignClient Bean 的岁月,就会运用这些参数去生成 Bean。流程图以下:

剖析 @FeignClient 定义的属性。 将表明@FeignClient 的属性 + 接口 StudyTimeFeignService的信息布局成一个 StudyTimeFeignService 的 beanDefinition。 尔后将 beanDefinition 转换成一个 holder,这个 holder 就是包孕了 beanDefinition, alias, beanName 信息。 最后将这个 holder 注册到 Spring 容器中。

源码以下:

 

// 生成 beanDefinition AbstractBeanDefinition beanDefinition = definition.getBeanDefinition(); // 转换成 holder,包孕了 beanDefinition, alias, beanName 信息 BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,     new String[] { alias }); // 注册到 Spring 凹凸文中。 BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry); 

 

上面我们已经晓得 FeignClient 的接口是怎么注册到 Spring 容器中了。后面服务要调用接口的岁月,就能间接用 FeignClient 的接口编制了,以下所示:

 

@Autowired private StudyTimeFeignService studyTimeFeignService;  // 省略部份代码 // 间接调用  studyTimeFeignService.getMemberStudyTimeListTest(id); 

 

然则我们并无细讲这个 FeignClient 的创立细节,上面我们看下 FeignClient 的创立细节,这个也是 OpenFeign 焦点道理。

七、OpenFeign 静态代理道理

上面的源码剖析中我们也提到了是由这个工厂类 FeignClientFactoryBean 来创立 FeignCient Bean,所以我们有须要对这个类举行分析。

在创立 FeignClient Bean 的进程中就会去生成静态代理工具。调用接口时,着实就是调用静态代理工具的编制来发起要求的。

阐发静态代理的入口编制为 getObject()。源码以下所示:

 

Targeter targeter = get(context, Targeter.class); return (T) targeter.target(this,定制衣服产品 builder, context,       new HardCodedTarget<>(this.type, this.name, url)); 

 

接着调用 target 编制这一块,内里的代码真的良多很细,我把焦点的代码拿出来给巨匠讲下,这个 target 会有两种完成类:

DefaultTargeter 和 HystrixTargeter。而岂论是哪种 target,都需求去调用 Feign.java 的 builder 编制去布局一个 feign client。

在布局的进程中,寄托 ReflectiveFeign 去布局。源码以下:

 

// 省略部份代码 public class ReflectiveFeign extends Feign {   // 为 feign client 接口中的每个接口编制创立一个 methodHandler  public <T> T newInstance(Target<T> target) {     for(...) {       methodToHandler.put(method, handler);     }     // 基于 JDK 静态代理的机制,创立了一个 passjava-study 接口的静态代理,全体对接口的调用都市被拦阻,尔后转交给 handler 的编制。     InvocationHandler handler = factory.create(target, methodToHandler);     T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(),           new Class<?>[] {target.type()}, handler); } 

 

ReflectiveFeign 做的事变就是为带有 @FeignClient 表明的接口,创立出接口编制的静态代理工具。

比喻示例代码中的接口 StudyTimeFeignService,会给这个接口中的编制 getMemberStudyTimeList 创立一个静态代理工具。

 

@FeignClient("passjava-study") public interface StudyTimeFeignService {     @RequestMapping("study/studytime/member/list/test/{id}")     public R getMemberStudyTimeList(@PathVariable("id") Long id); } 

 

创立静态代理的道理图以下所示:

剖析 FeignClient 接口上各个编制级其它表明,比喻近程接口的 URL、接口范例(Get、Post 等)、各个要求参数等。这里用到了 MVC Contract 和谈剖析,后面会讲到。

尔后将剖析到的数据封装成元数据,并为每个编制生成一个对应的 MethodHandler 类作为编制级其它代理。相当于把服务的要求地点、接口范例等都帮我们封装好了。这些 MethodHandler 编制会放到一个 HashMap 中。 尔后会生成一个 InvocationHandler 用来打点这个 hashMap,个中 Dispatch 指向这个 HashMap。 尔后运用 Java 的 JDK 原生的静态代理,完成为了 FeignClient 接口的静态代理 Proxy 工具。这个 Proxy 会增加到 Spring 容器中。 当要调用接口编制时,着实会调用静态代理 Proxy 工具的 methodHandler 来发送要求。

这个静态代理工具的布局以下所示,它包孕了全体接口编制的 MethodHandler。

8、剖析 MVC 表明的道理

上面我们讲到了接口上是有一些表明的,比喻 @RequestMapping,@PathVariable,这些表明统称为 Spring MVC 表明。然则由于 OpenFeign 是不睬解这些表明的,所以需求举行一次剖析。

剖析的流程图以下:

而剖析的类就是 SpringMvcContract 类,调用 parseAndValidateMetadata 举行剖析。剖析完当前,就会生成元数据列表。源码以下所示:

List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type()); 

这个类在这个门路下,巨匠可以或许自行翻阅下怎么剖析的,不在本篇的探究领域内。

https://github.com/spring-cloud/spring-cloud-openfeign/blob/main/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java 

这个元数据 MethodMetadata 内里有什么货物呢?

编制的定义,如 StudyTimeFeignService 的 getMemberStudyTimeList 编制。 编制的参数范例,如 Long。 发送 HTTP 要求的地点,如 /study/studytime/member/list/test/{id}。

尔后每个接口编制就会有对应的一个 MethodHandler,它内里就包孕了元数据,当我们调用接口编制时,理论上是调用静态代理工具的 MethodHandler 来发送近程调用要求的。

上面我们针对 OpenFeign 框架怎么为 FeignClient 接口生成静态代理已经讲完了,上面我们再来看下当我们调用接口编制时,静态代理工具是怎么发送近程调用要求的。

9、OpenFeign 发送要求的道理

先下贱程图:

照旧在 ReflectiveFeign 类中,有一个 invoke 编制,会执行下列代码:

dispatch.get(method).invoke(args); 

这个 dispatch 我们从前已经解说过了,它指向了一个 HashMap,内里包孕了 FeignClient 每个接口的 MethodHandler 类。

这行代码的意义就是痛处 method 找到 MethodHandler,调用它的 invoke 编制,并且传的参数就是我们接口中的定义的参数。

那我们再跟出来看下这个 MethodHandler invoke 编制内里做了什么事变。源码以下所示:

public Object invoke(Object[] argv) throws Throwable {   RequestTemplate template = buildTemplateFromArgs.create(argv);   ... } 

我们可以或许看到这个编制内里生成了 RequestTemplate,它的值近似以下:

GET /study/list/test/1 HTTP/1.1 

RequestTemplate 转换成 Request,它的值近似以下:

GET http://passjava-study/study/list/test/1 HTTP/1.1 

这门路方就是我们要 study 服务的编制,这样就能间接调用到 study 服了呀!

OpenFeign 帮我们组装好了发起近程调用的通通,我们只管调用就行了。

接着 MethodHandler 会执行下列编制,发起 HTTP 要求。

client.execute(request, options); 

从上面的我们要调用的服务就是 passjava-study,然则这个服务的具体 IP 地点我们是不晓得的,那 OpenFeign 是怎么获失去 passjava-study 服务的 IP 地点的呢?

回顾转头转头回忆下最起头我们提出的焦点成就:OpenFeign 是怎么举行负载均衡的?

我们是否可以或许遥想到上一讲的 Ribbon 负载均衡,它方就是用来做 IP 地点抉择的么?

那我们就来看下 OpenFeign 又是怎么和 Ribbon 举行整合的。

10、OpenFeign 怎么与 Ribbon 整合的道理

为了验证 Ribbon 的负载均衡,我们需求启动两个 passjava-study 服务,这里我启动了两个服务,端口号划分为 12100 和 12200,IP 地点都是本机 IP:192.168.10.197。

接着上面的源码延续看,client.execute() 编制着实会调用 LoadBalancerFeignClient 的 exceute 编制。

这个编制内里的执行流程以下图所示:

将服务名称 passjava-study 从 Request 的 URL 中删掉,剩下的以下所示:
GET http:///study/list/test/1 HTTP/1.1 
痛处服务名从缓存中找 FeignLoadBalancer,假定缓存中没有,则创立一个 FeignLoadBalancer。 FeignLoadBalancer 会创立出一个 co妹妹and,这个 co妹妹and 会执行一个 sumbit 编制。 submit 编制内里就会用 Ribbon 的负载均衡算法抉择一个 server。源码以下:
Server svc = lb.chooseServer(loadBalancerKey); 

经由过程 debug 调试,我们可以或许看到两主要求的端口号不一样,一个是 12200,一个是 12100,分析白凿举行了负载均衡。

尔后将 IP 地点和从前剔除去服务名称的 URL 举行拼接,生成最后的服务地点。 最后 FeignLoadBalancer 执行 execute 编制发送要求。

那巨匠有无疑问,Ribbon 是怎么拿到服务地点列表的?这个就是上一讲 Ribbon 架构内里的内容。

Ribbon 的焦点组件 ServerListUpdater,用来同步注册表的,它有一个完成类 PollingServerListUpdater ,专门用来做守时同步的。默认1s 后执行一个 Runnable 线程,后面就是每隔 30s 执行 Runnable 线程。这个 Runnable 线程就是去取得注册左右的注册表的。

11、OpenFeign 处理惩罚照顾的道理

当近程服务 passjava-study 处理惩罚完业务逻辑后,就会前去 reponse 给 passjava-member 服务了,这里还会对 reponse 举行一次解码操作。

Object result = decode(response); 

这个内里做的事变就是调用 ResponseEntityDecoder 的 decode 编制,将 Json 字符串转化为 Bean 工具。

十2、总结

本文经由过程我的开源名目 PassJava 中用到的 OpenFeign 作为示例代码作为入口举行解说。尔后以图解+解读源码的编制深入分析了 OpenFeign 的运起色制和架构策画。

焦点志向: OpenFeign 会扫描带有 @FeignClient 表明的接口,尔后为其生成一个静态代理。 静态代理内里包孕有接口编制的 MethodHandler,MethodHandler 内里又包孕颠末 MVC Contract 剖析表明后的元数据。 发起要求时,MethodHandler 会生成一个 Request。 负载均衡器 Ribbon 会从服务列表入选取一个 Server,拿到对应的 IP 地点后,拼接成最后的 URL,就能发起近程服务调用了。

OpenFeign 的焦点流程图:

 



我的网站