openfeign的重试机制如何保证幂等性(openfeign使用)

点击关注,快速进阶高级架构师

作者:亦山札记

什么是Feign?

Feign 的英文表意为“假装,伪装,变形”, 是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,这种请求相对而言比较直观。

Feign被广泛应用在Spring Cloud 的解决方案中,是学习基于Spring Cloud 微服务架构不可或缺的重要组件。

开源项目地址:

Feign解决了什么问题?

封装了Http调用流程,更适合面向接口化的变成习惯

在服务调用的场景中,我们经常调用基于Http协议的服务,而我们经常使用到的框架可能有HttpURLConnection、Apache HttpComponnets、OkHttp3 、Netty等等,这些框架在基于自身的专注点提供了自身特性。而从角色划分上来看,他们的职能是一致的提供Http调用服务。具体流程如下:

Spring Cloud Feign设计原理

Feign是如何设计的?

Spring Cloud Feign设计原理

PHASE 1. 基于面向接口的动态代理方式生成实现类

在使用feign 时,会定义对应的接口类,在接口类上使用Http相关的注解,标识HTTP请求参数信息,如下所示:

在Feign 底层,通过基于面向接口的动态代理方式生成实现类,将请求调用委托到动态代理实现类,基本原理如下所示:

Spring Cloud Feign设计原理

PHASE 2. 根据Contract协议规则,解析接口类的注解信息,解析成内部表现:

Spring Cloud Feign设计原理

Feign 定义了转换协议,定义如下:

默认Contract 实现

Feign 默认有一套自己的协议规范,规定了一些注解,可以映射成对应的Http请求,如官方的一个例子:

上述的例子中,尝试调用GitHub.getContributors(“foo”,”myrepo”)的的时候,会转换成如下的HTTP请求:

GET /repos/foo/myrepo/contributorsHOST XXXX.XXX.XXX

Feign 默认的协议规范

具体FeignContract 是如何解析的,不在本文的介绍范围内,详情请参考代码:

基于Spring MVC的协议规范SpringMvcContract:

当前Spring Cloud 微服务解决方案中,为了降低学习成本,采用了Spring MVC的部分注解来完成 请求协议解析,也就是说 ,写客户端请求接口和像写服务端代码一样:客户端和服务端可以通过SDK的方式进行约定,客户端只需要引入服务端发布的SDK API,就可以使用面向接口的编码方式对接服务:

Spring Cloud Feign设计原理

我们团队内部就是按照这种思路,结合Spring Boot Starter 的特性,定义了服务端starter,

服务消费者在使用的时候,只需要引入Starter,就可以调用服务。这个比较适合平台无关性,接口抽象出来的好处就是可以根据服务调用实现方式自有切换:

  • 可以基于简单的Http服务调用;
  • 可以基于Spring Cloud 微服务架构调用;
  • 可以基于Dubbo SOA服务治理

这种模式比较适合在SaSS混合软件服务的模式下自有切换,根据客户的硬件能力选择合适的方式部署,也可以基于自身的服务集群部署微服务

至于Spring Cloud 是如何实现 协议解析的,可参考代码:

当然,目前的Spring MVC的注解并不是可以完全使用的,有一些注解并不支持,如@GetMapping,@PutMapping 等,仅支持使用@RequestMapping 等,另外注解继承性方面也有些问题;具体限制细节,每个版本能会有些出入,可以参考上述的代码实现,比较简单。

Spring Cloud 没有基于Spring MVC 全部注解来做Feign 客户端注解协议解析,个人认为这个是一个不小的坑。在刚入手Spring Cloud 的时候,就碰到这个问题。后来是深入代码才解决的…. 这个应该有人写了增强类来处理,暂且不表,先MARK一下,是一个开源代码练手的好机会。

PHASE 3. 基于 RequestBean,动态生成Request

根据传入的Bean对象和注解信息,从中提取出相应的值,来构造Http Request 对象:

Spring Cloud Feign设计原理

PHASE 4. 使用Encoder 将Bean转换成 Http报文正文(消息解析和转码逻辑)

Feign 最终会将请求转换成Http 消息发送出去,传入的请求对象最终会解析成消息体,如下所示:

Spring Cloud Feign设计原理

在接口定义上Feign做的比较简单,抽象出了Encoder 和decoder 接口:

目前Feign 有以下实现:

PHASE 5. 拦截器负责对请求和返回进行装饰处理

在请求转换的过程中,Feign 抽象出来了拦截器接口,用于用户自定义对请求的操作:

比如,如果希望Http消息传递过程中被压缩,可以定义一个请求拦截器:

PHASE 6. 日志记录

在发送和接收请求的时候,Feign定义了统一的日志门面来输出日志信息 , 并且将日志的输出定义了四个等级:

PHASE 7 . 基于重试器发送HTTP请求

Feign 内置了一个重试器,当HTTP请求出现IO异常时,Feign会有一个最大尝试次数发送请求,以下是Feign核心

代码逻辑:

重试器有如下几个控制参数:

具体的代码实现可参考:

PHASE 8. 发送Http请求

Feign 真正发送HTTP请求是委托给 feign.Client 来做的:

Feign 默认底层通过JDK 的 java.net.HttpURLConnection 实现了feign.Client接口类,在每次发送请求的时候,都会创建新的HttpURLConnection 链接,这也就是为什么默认情况下Feign的性能很差的原因。可以通过拓展该接口,使用Apache HttpClient 或者OkHttp3等基于连接池的高性能Http客户端,我们项目内部使用的就是OkHttp3作为Http 客户端。

如下是Feign 的默认实现,供参考:

Feign 的性能怎么样?

Feign 整体框架非常小巧,在处理请求转换和消息解析的过程中,基本上没什么时间消耗。真正影响性能的,是处理Http请求的环节。

如上所述,由于默认情况下,Feign采用的是JDK的HttpURLConnection,所以整体性能并不高,刚开始接触Spring Cloud 的同学,如果没注意这些细节,可能会对Spring Cloud 有很大的偏见。

我们项目内部使用的是OkHttp3 作为连接客户端。

本站部分内容由互联网用户自发贡献,该文观点仅代表作者本人,本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如发现本站有涉嫌抄袭侵权/违法违规等内容,请联系我们举报!一经查实,本站将立刻删除。