SpringCloud与微服务-第6章-声明式服务调用(OpenFeign)

Spring Cloud OpenFeign 基于 Netflix Feign 实现,整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix ,除了提供这两者的强大功能之外,它还提供了一种声明式的 Web 服务客户端定义方式,让我们可以像使用本地方法一样来进行远程服务调用。

快速入门

通过一个简单的示例来展现 Spring Cloud Feign 在服务客户端定义上所带来的便利。

下面的示例将继续使用之前我们实现的 order-service 服务,这里我们会通过 Spring Cloud Feign 提供的声明式服务绑定功能来实现对该服务接口的调用。

配置代码

1.引入依赖

orderservice 服务的 pom.xml 文件中,引入如下依赖:

openfeig 依赖中已然包含了 ribbon 的依赖


2.启用 Feign

在 orderservice 的启动类上,添加注解@EnableFeignClients


3.定义服务接口

定义一个接口,通过@FeignClient 注解指定服务名来绑定服务,然后再使用 Spring MVC 的注解来绑定具体该服务提供的 REST 接口。


注意:
注解中的服务名不区分大小写,所以使用 USERSERVICEuserservice 都 是可以的。

注意接口的访问路径要完整, 如果匹配的路径不对, 会报 404 错误.

4.创建 Controller

创建一个 Controller 来实现对 Feign 客户端的调用。使用 @Autowired 直接注入上面定义的 UserClient 实例,并在 helloConsumer 函数中调用这个绑定了 userservice 服务接口的客户端来向该服务发起接口的调用。



测试验证

如之前验证 Ribbon 客户端负载均衡一样,我们先启动服务注册中心 Nacos 以及 orderservice 服务和多个 userservice 服务。此时我们在 Nacos 信息面板中可看到 如下内容:


image-20220725233238207


发送几次 GET 请求到 http://localhost:8080/feign/order/{orderId}, 可以得到如 之前 Ribbon 实现时一样的效果,正确返回了 userservice 的数据。

image-20220725233533961


并且根据控制台的输出,我们可以看到 Feign 实现的消费者,依然是利用 Ribbon 维护了针对 userservice 的服务列表信息,并且通过轮询实现了客户端负载均衡。而与 Ribbon 不同的是,通过 Feign 我们只需定义服务绑定接口,以声明式的方法,优雅而简单地实现了服务调用。

Feign 使用起来看起来更像是一个本地接口的实现,而不是一个远程服务的调用。这样的设计使得我们在使用 Feign 的时候,可以像调用本地方法一样调用远程服务。

小问题 :
RestTemplate 己经实现了对 __ 请求的封装处理,形成了一套模板化的调用方法?

  1. FTP
  2. HTTP
  3. TCP
  4. IP
答案

参数绑定

在本节中,我们将详细介绍 Feign 中对几种不同形式参数的绑定方法。


扩展一下服务提供方 orderservice 。增加下面这些接口定义,其中包含:

  1. 带有 Request 参数的请求
  2. 带有 Header 信息的请求
  3. 带有 RequestBody 的请求并且请求响应体中是一个对象的请求。


实体类 User 的定义如下:

需要注意的是,这里必须要有 User 的默认构造函数。不然, Spring Cloud Feign 根据 JSON 字符串转换 User 对象时会抛出异常。我们使用 Lombok 插件的 @Data 注解, 可以避免编写标准 java bean 的繁琐步骤,同时提供相应的默认构造。


在完成了对 orderservice 的改造之后,下面我们开始在快速入门示例的 UserClient 接口中实现这些新增的请求的绑定。



最后,在 Controller 中新增方法,来对本节新增的声明接口进行调用,修改后的完整代码如下所示:



测试验证

在完成上述改造之后,启动服务注册中心 Nacos、order-service 服务以及 user-service 服务 。通过浏览器访问 order-service 的 FeignConsumerController 的 requestParamDemo 方法, 触发新增接口的调用。最终,我们会获得如下输出,代表接口绑定和调用成功:

image-20220726111444264


其他两种参数绑定的调用,我们可以使用 IDEA 的一款小插件FastRequest进行测试:(FastRequest 是收费插件, 可以用免费插件 RestfulTool 代替)

添加请求头:

image-20220726112008604


点击发送并查看响应结果:

image-20220726112113576


同样的, RequestBody 的参数也可以添加请求体,点击发送按钮进行测试,结果如下:

image-20220726111906304

实践方案

我们几乎完全可以从服务提供方的 Controller 中依靠复制操作,构建出相应的服务客户端绑定接口, 然后添加@FeignClient 注解即可。使用的时候,直接注入到 Controller 中即可。

这部分内容是否可以得到进一步的抽象呢?在 Spring Cloud Feign 中,针对该问题我们通常有两种解决方案,以进一步减少编码量。

  1. 通过继承的方式实现接口定义的共享
  2. 创建独立的 Api 模块, 通过依赖的方式实现接口定义的共享

继承方案

一样的代码可以通过继承来共享:

  1. 定义业务相关接口,利用接口定义,并基于 SpringMVC 注解做声明。

    • 注意定义 MappingUrl 时,需要使用绝对路径,否则会出现 404 错误。
  2. Feign 客户端和 Controller 都继承该接口。

    • 这样接口实际只需要定义一次,就可以在 Feign 客户端和 Controller 中使用。免去了复制操作。

image-20220726151709040

图示中, UserClient 继承了 UserApi 接口。这样,我们就可以在 UserClient 中直接使用 UserApi 中定义的接口,而不需要再次定义。在 Controller 中, 也可以直接注入 UserClient,并调用其中的方法。


使用 Spring Cloud Feign 继承特性的优点很明显,可以将接口的定义从 Controller 中剥离,实现在构建期的接口绑定,从而有效减少服务客户端的绑定配置。这么做虽然可以很方便地实现接口定义和依赖 的共享,不用再复制粘贴接口进行绑定.

但是这样通过共享接口的做法使用不当的话会带来副作用。


由于接口在构建期间就建立起了依赖,所以微服务之间的耦合度会变得很高。因此接口变动就会对项目构建造成影响。

可能服务提供方修改了一个接口定义,那么会直接导致客户端工程的构建失败。所以,如果开发团队通过此方法来实现接口共享的话,建议在开发评审期间严格遵守面向对象的开闭原则,尽可能地做好前后版本的兼容,防止牵一发而动全身的后果,增加团队不必要的维护工作量。


基于以上原因,官方并不推荐在项目中使用继承方案,而是更推荐我们由服务提供方新建立一个 API 服务,提供给所有的消费者使用。接下来,我们将会详细讲解这种实践方案。


独立模块

将 Feign 的 Client 抽取为独立的微服务模块,并且把接口有关的 POJO、默认的 Feign 配置都放到这个微服务模块中,提供给所有消费者微服务项目使用。例如,将 UserClient、User、Feign 的默认配置都抽取到一个 feign-api 包中,所有微服务引用该依赖包,即可直接使用。


下面是构建 feign-api 的具体步骤:

w:35em


1.创建 module

首先创建一个 module,命名为 feign-api:

image-20220726175321856


2.引入依赖

在 feign-api 中然后引入 feign 的 starter 依赖


然后,order-service 中编写的 UserClient、User 都复制到 feign-api 项目中。

image-20220726175513116


通过 maven 的 install 对项目进行构建和安装,将 feign-api 安装到本地仓库中。


3.使用 feign-api

在 order-service 的 pom 文件中从本地 maven 仓库中引入 feign-api 的依赖:

修改 order-service 中的所有与上述三个组件有关的导包部分,改成导入 feign-api 中的包


4.修改启动类

由于 feign-api 与 order-service 项目的包路径并不完全一致,为了解决 spring boot 工程默认包扫描策略导致无法注入问题,需要在启动类上声明所用的 feign 客户端的列表。 修改 order-service 的启动类,补充@EnabledFeignClients 的属性内容。


5.启动测试

现在,像上一小节参数绑定中的案例一样,依次启动服务注册中心 Nacos,order-service 与 user-service 服务进行测试。测试结果如下:

image-20220726180548601


添加 Ribbon 配置

由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以直接通过配置 Ribbon 客户端的方式来自定义各个服务客户端调用的参数。那么我们 如何在使用 Spring Cloud Feign 的工程中使用 Ribbon 的配置呢?


全局 Ribbon 配置

全局配置的方法非常简单,我们可以直接在配置文件中设置:


对具体服务进行 Ribbon 配置

在配置文件中, 除了针对所有服务进行全局配置, 还可以针对特定的服务进行个性化配置.


在定义 Feign 客户端的时候,我们使用了 @FeignClient 注解。在初始化 过程中,Spring Cloud Feign 会根据该注解的 name 属性或 value 属性指定的服务名,自动创建一个同名的 Ribbon 客户端。

例如使用 @FeignClient(value ="userservice")来创建 Feign 客户端的时候,同时也创建了一个名为 userservice 的 Ribbon 客户端。


既然如此,我们就可以使用 @FeignClient 注解中指定的服务提供者名称来设置对应的 Ribbon 参数,比如:


添加 Ribbon 重试机制

在 Spring Cloud Feign 中默认实现了请求的重试机制,而上面我们对于 orderservice 客户端的配置内容就是对于请求超时以及重试机制配置的详情,具体内 容可参考第 4 章最后一节关于 Spring Cloud Ribbon 重试机制的介绍。

这里需要注意一点,Ribbon 的超时与 Hystrix 的超时是两个概念。为了让上述实现有效,我们需要让 Hystrix 的超时时间大于 Ribbon 的超时时间,否则 Hystrix 命令超时后,该命令直接熔断,重试机制就没有任何意义了。


hystrix 的超时时间配置如下:

Ribbon 的超时时间配置如下:


添加 Hystrix 配置

在 Spring Cloud Feign 中,除了引入了用于客户端负载均衡的 Spring Cloud Ribbon 之外,还引入了服务保护与容错的工具 Hystrix 。

默认情况Spring Cloud Feign 是关闭Hystrix支持的, 因此需要手动启用Hystrix。开启Hystrix后所有 Feign 客户端的方法都会封装成为 Hystrix 命令中进行服务保护。


在对 Hystrix 进行配置之前,我们需要确认 feign.hystrix.enabled 参数需要先设置为 true, 否则默认会关闭 Feign 客户端的 Hystrix 支持。

默认 feign.hystrix.enabled=false, Hystrix 支持是关闭的

使用 hystrix.command.default.execution.timeout.enabled=false 关闭超时熔断


添加 Hystrix 全局配置

对于 Hystrix 的全局配置同 Spring Cloud Ribbon 的全局配置一样,直接使用它的默认配置前缀 hystrix.command.default 就可以进行设置,比如设置全局的超时时间:


局部禁用 Hystrix

如果全局启用 Hystrix 支持,但是想要针对某个服务客户端关闭 Hystrix 支持时,可以通过使用 @Scope("prototype") 注解为指定的客户端配置 Feign.Builder 实例.

详细实现步骤如下所示。


1.构建一个配置类


2.引入配置到 Feign 客户端

在 UserClient 的@FeignClient 注解中,通过 configuration 参数引入上面实现的配置。

这样,就可以针对指定的 Feign 客户端来关闭 Hystrix 支持了。

原理是通过 @Scope("prototype") 注解为每个客户端创建一个新的实例,而此实例不受 Hystrix 配置的影响。


通过 Hystrix 命令的键值进行配置

对于 Hystrix 命令的配置,在实际应用时往往也会根据实际业务情况制定出不同的配置方案。配置方法也跟传统的 Hystrix 命令的参数配置相似,采用 hystrix.command.<commandKey> 作为前缀。而 <commandKey> 默认情况下会采用 Feign 客户端中的方法名作为标识,所以,针对上一节介绍的重试机制中对 /showAppInfo 接口的熔断超时时间的配置可以通过其方法名作为 <commandKey> 来进行配置.


具体如下:

在使用指定命令配置的时候,需要注意,由于方法名很有可能重复,这个时候相同方法名的 Hystrix 配置会共用,所以在进行方法定义与配置的时候需要做好一定的规划。


服务降级配置

Hystrix 提供的服务降级是服务容错的重要功能, Spring Cloud Feign 提供了一种简单的定义方式,可以优雅的完成服务降级的配置。下面我们在之前创建的 feign-api 工程中进行改造。

  • 服务降级逻辑的实现只需要为 Feign 客户端的定义接口编写一个具体的接口实现类。 比如为 UserClient 接口实现一个服务降级类 UserClientFallback ,其中每个重写方法的实现逻辑都可以用来定义相应的服务降级逻辑,具体如下:


  • 在服务绑定接口 UserClient 中,通过 @FeignClient 注解的 fallback 属性来指定对应的服务降级实现类。


  • 在 order-service 服务的启动类上使用@Bean 注解声明服务降级类,解决 UserClientFallback 实例无法找到异常


上面的做法,是使用了 feign-api 提供的默认的统一服务降级配置类, 对每个接口都配置了对应的服务降级逻辑.

如果,order-service 想自定义某个服务降级逻辑, 只需在 order-service 中创建一个新的类,继承 UserClientFallback,重写其方法, 注入到 spring 容器里即可:


创建新的自定义服务降级类:


重新注入到 Spring 容器:


测试验证

下面我们来验证一下服务降级逻辑的实现。启动服务注册中心 Nacos 和 order-service, 但是不启动 user-service 服务。发送 GET 请求到 http://localhost:8080/feign/order/request?param=world, 因为 user-service 服务没有启动,会直接触发服务降级.

如果没有降级发生, 检查order-service是否在springboot应用配置文件中启用hystrix:

注意 springcloud2020.0.1之后的版本改为 feign.circuitbreaker.enabled=true


正如我们在 MyUserClientFallback 类中实现的内容,每一个服务接口的断路器实际就是实现类中的重写函数的实现。


小问题:

在 Spring Cloud Feign 中,可以通过 _____ 来关闭 Hystrix 功能?

  1. feign.hystrix.enabled=false
  2. feign.hystrix.hystrixCommand=false
  3. feign.hystrix.disabled=false
  4. feign.hystrix.hystrix=false
答案


其他配置


请求压缩

Feign 是通过 http 调用的,那么就牵扯到一个数据大小的问题。如果不经过压缩就发送请求、获取响应,那么会因为流量过大导致浪费流量,这时就需要使用数据压缩,将大流量压缩成小流量。

Spring Cloud Feign 支持对请求与响应进行 GZIP 压缩,以减少通信过程中的性能损耗。


配置请求压缩支持, 找到需要调用 feign 的服务消费者 order-service 模块配置文件中添加如下配置,即可开启:


同时,我们也可以对请求的数据类型,以及触发压缩的大小下限进行设置:

值得注意的是,上面的数据类型、压缩大小下限均为默认值。


日志配置

Spring Cloud Feign 在构建服务客户端时,会为每一个客户端都创建一个 feign. Logger 实例,我们可以利用该日志对象的 DEBUG 模式来帮助分析 Feign 的请求细节。

对于 Feign 的 Logger 级别主要有下面 4 类,可根据实际需要进行调整使用。

  • NONE :不记录任何信息。
  • BASIC :仅记录请求方法、 URL 以及响应状态码和执行时间。
  • HEADERS :除了记录 BASIC 级别的信息之外,还会记录请求和响应的头信息。
  • FULL :记录所有请求与响应的明细,包括头信息、请求体、元数据等

配置 Feign 日志有两种方式:

  • 配置文件方式
  • Java 配置类方式

日志配置可以配置全局生效,也可以配置针对某个微服务生效,下面我们详细介绍两种方式是如何开启日志配置。


配置文件方式

通过修改服务消费方的 application.yml 文件,配置 Spring Cloud Feign 的日志级别。


全局 FeignClient 配置:


针对实例的 FeignClient 配置:


我们以 FULL 级别的日志配置为例,发送一次请求测试,查看控制台日志输出,可以看到日志信息非常完整:

image-20220727163823119


通过 Java 配置类方式配置 FeignClient

需要先声明一个 Bean,用来定义日志的级别。


使用 Java 配置类进行全局 FeignClient 配置

全局配置,则需要在服务消费方的主启动类上进行配置指定:


使用 Java 配置类进行局部 FeignClient 配置

局部配置,则放在相应的 feign 客户端的 @FeignClient 这个注解中:

值得注意的是,当使用配置文件和使用 Java 代码两种方式同时存在的时候,配置文件的优先级更高。当然,我们不推荐同时配置两种方式,只保留一种方式即可。


活动 6.1:

使用 Feign 实现服务远程调用


练习问题

  1. 在服务绑定接口 HelloService 中,通过 @FeignClient 注解的 _____ 属性来指定对应的服务降级实现类。
    a. fallback
    b. back
    c. fullback
    d. fall

    查看答案


  1. 对于 Hystrix 的全局配置同 Spring Cloud Ribbon 的全局配置一样,直接使用它的默认配置前缀 _____ 就可以进行设置
    a. hystrix.command.index
    b. hystrix.command.default
    c. hystrix.default.command
    d. hystrix.index.command

    查看答案


  1. Ribbon 的超时与 Hystrix 的超时是两个概念。为了让上述实现有效,我们需要让 Hystrix 的超时时间 _____ Ribbon 的超时时间?
    a. 小于
    b. 等于
    c. 大于
    d. 不等于

    查看答案


  1. 我们在使用 Spring Cloud Ribbon 时,通常都会利用它对 _____ 的请求拦截来实现对依赖服务的接口调用。
    a. FeignTemplate
    b. SpringTemplate
    c. CloudTemplate
    d. RestTemplate

    查看答案


小结

在本章中,您学习了:

  • 我们在使用 Spring Cloud Ribbon 时,通常都会利用它对 RestTemplate 的请求拦截来实现对依赖服务的接口调用,而 RestTemplate 己经实现了对 HTTP 请求的封装处理,形成了一套模板化的调用方法。
  • 在 Spring Cloud Feign 的实现下,我们只需创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了在使用 Spring Cloud Ribbon 时自行封装服务调用客户端的开发量。
  • Spring Cloud Feign 具备可插拔的注解支持, 包括 Feign 注解和 JAX-RS 注解。同时,为了适应 Spring 的广大用户,它在 NetflixFeign 的基础上扩展了对 Spring MVC 的注解支持。

  • 由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以直接通过配置 Ribbon 客户端的方式来自定义各个服务客户端调用的参数。
  • 在 Spring Cloud Feign 中,除了引入了用于客户端负载均衡的 Spring Cloud Ribbon 之外,还引入了服务保护与容错的工具 Hystrix。
  • 在对 Hystrix 进行配置之前,我们需要确认 feign.hystrix.enabled 参数没有被设置为 false, 否则该参数设置会关闭 Feign 客户端的 Hystrix 支持。
  • Hystrix 提供的服务降级是服务容错的重要功能。

关于 Spring Cloud Feign,以下哪一项说法错误的? A.Feign 采用的是基于接口的注解, 要想开启相关功能, 需要在启动类上加 @EnableFeignClients B.Feign 注解 @FeignClient(name=“xxx”) 可以指定调用 xxx 服务 C.Feign 整合了 Hystrix,具有熔断的能力 D.Ribbon 是一个负载均衡客户端,可以很好的控制 HTTP 和 TCP 的一些行为,Ribbon 默认集成了 Feig 答案:D Ribbon和Feign的区别说法错误的是()。 A.启动类使用的注解不同,Ribbon用的是@RibbonClients,Feign用的是@FeignClients。 B.服务的指定位置不同,Ribbon是在@RibbonClient注解上声明,Feign则是在定义抽象方法的接口中使用@FeignClient声明。 C.调用方式不同,Ribbon需要自己构建http请求,模拟http请求然后使用RestTemplate发送给其他服务,步骤相当繁琐。 D.Feign则是在Ribbon的基础上进行了一次改进,采用接口的方式,将需要调用的其他服务的方法定义成抽象方法即可,不需要自己构建http请求。不过要注意的是抽象方法的注解、方法签名要和提供服务的方法完全一致。 答案:A 2. 关于OpenFeing的说法,下列说法正确的是: a. OpenFeign是一个声明式的Web服务客户端使得编写Web服务客户端变得更加简单 b. 只需要创建一个接口并用@FeignClient注解进行,即可完成对服务提供方的接口绑定 c. OpenFeign整合了Ribbon和Hystrix,具有可插拔的注解支持 d. 都对 答案: d 判断题: 1. 在 Spring Cloud Feign 的实现下,我们只需创建一个接口并用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了在使用 Spring Cloud Ribbon 时自行封装服务调用客户端的开发量。
查看答案

2. Spring Cloud Feign 具备可插拔的注解支持, 包括 Feign 注解和 JAX-RS 注解。同时,为了适应 Spring 的广大用户,它在 NetflixFeign 的基础上扩展了对 Spring MVC 的注解支持。
查看答案

3. 由于 Spring Cloud Feign 的客户端负载均衡是通过 Spring Cloud Ribbon 实现的,所以我们可以直接通过配置 Ribbon 客户端的方式来自定义各个服务客户端调用的参数。
查看答案

4. 在对 Hystrix 进行配置之前,我们需要确认 feign.hystrix.enabled 参数没有被设置为 false, 否则该参数设置会关闭 Feign 客户端的 Hystrix 支持。
查看答案

5. Hystrix 提供的服务降级是服务容错的重要功能。
查看答案

Views: 12