通过前面的学习,我们基本掌握了微服务架构中如何使用  SpringCloud Ribbon 和 SpringCloud Hystrix 来实现客户端负载均衡的服务调用、通过断路器来保护我们的微服务应用。接下来,介绍一款重磅武器 SpringCloud Feign,它是更高层次的封装并简化了以上两个基本工具。它不仅整合了 SpringCloud Ribbon 和 SpringCloud Hystrix,还提供了一种声明式的 Web 服务客户端定义方式

问:为什么使用 Feign?

答:我们在使用 Ribbon 的时候,通常利用它对 RestTemplate 的请求拦截来实现对依赖服务的接口调用,RestTemplate 是对 Http 请求进行封装过的。在实际项目中,我们对服务的调用往往有多处,如果还使用 RestTemplate,我们就需要在每一处地方都注入,俗称“重复造轮子”。使用 SpringCloud Feign,我们只需创建一个接口并用注解的方式配置它,就可以完成对服务提供方的接口绑定,简化了使用 Ribbon 时自行封装服务调用客户端的开发量。Feign 为了适应 Spring 的广大用户,还在 Netflix Feign 的基础上增加对 SpringMVC 的注解支持,对一些编码器和解码器,它还可以拔插式方式提供,这些都是福音啊。

 

接下来,开始学习 SpringCloud Feign。

首先创建一个 feign-consumer 模块

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_Cloud

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_SpringCloud_02

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_微服务架构_03

pom.xml 配置文件中,加入 spring-cloud-starter-openfeign 依赖,完整代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>MyProject</artifactId>
        <groupId>com.study</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>feign-consumer</artifactId>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>${netflix.eureka.client.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

    </dependencies>

</project>

 创建 com.study 包,新建 FeignConsumerApplication 启动类,加上 @EnableFeignClients注解开启 Feign 的功能。

注意:如果未能识别 @EnableFeignClients注解,一般是 SpringCloud 版本选择不对的问题!按照本系列教程的来,版本选择 Finchley.SR2。之前笔者试过 Finchley.SR4 版本,一直未能识别 @EnableFeignClients注解,且网上寻找了一番,无果。为了降低学习成本,请保持与本系列教程版本一致!

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_Feign_04

完整代码如下:

package com.study;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

/**
 * @author biandan
 * @signature 让天下没有难写的代码
 * @create 2019-10-21 下午 11:06
 */
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(FeignConsumerApplication.class,args);
    }
}

接下来,创建一个接口:SayHelloService,添加注解 @FeignClient(“服务名”) 指定服务名来绑定服务,然后再使用 SpringMVC 的注解来绑定具体该服务提供的 Rest 接口。我们先在 study 包上创建一个 service 包,再创建 SayHelloService 接口。

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_SpringCloud_05

 

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_微服务架构_06

 

注意,我们添加的  @FeignClient(“服务名”)

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_Feign_07

 

完整 SayHelloService 接口的代码如下:

package com.study.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author biandan
 * @signature 让天下没有难写的代码
 * @create 2019-10-24 下午 11:29
 */
@FeignClient("eureka-client-biandan")
public interface SayHelloService {

    //调用上述服务的 say() 方法
    @RequestMapping("/say")
    String sayHelloFrom();
}

接下来,我们创建一个类:FeignController 来调用 Feign 客户端的调用,使用 @Autowired 注解直接注入上面定义的 SayHelloService 实例,然后定义一个方法去调用绑定了 eureka-client-biandan

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_微服务架构_08

 

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_Feign_09

 

FeignController  完整代码如下:

说明:我们看到 sayHelloService 实例报错,可以忽略这个编译报错。因为这个实例是在服务启动的时候才注入。如果有强迫症要去掉这个编译报错,可以把 @Autowired 改成:@Autowired(required = false)

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_spring_10

package com.study.controller;

import com.study.service.SayHelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author biandan
 * @signature 让天下没有难写的代码
 * @create 2019-10-24 下午 11:47
 */
@RestController
public class FeignController {

    @Autowired(required = false)
    private SayHelloService sayHelloService;

    @RequestMapping(value = "/feign")
    public String feignConsumer(){
        return sayHelloService.sayHelloFrom();
    }
}

接下来,在 eureka-feign 模块创建一个 application.yml 配置文件,端口:10001

# 这是客户端服务的配置节点
server:
  port: 10001

eureka:
  instance:
    hostname: main.study.com
    lease-renewal-interval-in-seconds: 30
    lease-expiration-duration-in-seconds: 90
  client:
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8000/eureka/

# 服务的名字
spring:
  application:
    name: eureka-feign-client

注意,我们之前在测试 Ribbon 的时候,在 eureka-client 模块的 EurekaClientApplication 增加了一个线程休眠的代码,需要注释掉:

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_Feign_11

 

接下来,像测试 Ribbon 一样,我们依次启动服务:注册中心(EurekaServerApplication)、注册中心集群(EurekaServerClusterApplication 可不启动)、服务提供者(EurekaClientApplication 9000端口、然后修改端口号=9001,再启动,一共2个服务。具体方法见之前的博客)、Feign服务消费者(FeignConsumerApplication)。

浏览器访问地址:http://main.study.com:10001/feign  浏览器显示结果(多次刷新访问,交替显示结果):

eureka-client-biandan 说:让天下没有难写的代码!from port =9000
eureka-client-biandan 说:让天下没有难写的代码!from port =9001

上述结果验证了 Feign 具有 Ribbon 的负载均衡功能

 

Ribbon 配置

问:如何在使用 SpringCloud Feign 的工程中使用 Ribbon 的配置?

答:常规的方法有以下 2 种

1、Ribbon 的全局配置

全局配置的方法比较简单,直接使用 ribbon.<key> = <value> 的方式设置 Ribbon 的各项参数即可。比如我们设置 Ribbon 的超时时间,可以在 eureka-feign 模块的 application.yml 配置文件中这样配置:

ribbon:
  ConnectTimeout: 1500
  ReadTimeout: 1500

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_SpringCloud_12

2、指定服务的配置

有时候我们需要对某个服务的超时时间做实际的调整,这时候就需要指定服务来配置。基本配置语法如下:

<server>.ribbon.key = value

比如,我们需要对 eureka-client-biandan 这个服务配置超时时间

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_Feign_13

有人喜欢抬杠了,问:如果有全局配置和指定服务配置,会使用哪个配置呢?

答:经过测试,证实了使用指定服务配置。一般的系统配置都这样:指定配置 > 全局配置。

 

我们改造一下 eureka-client 模块的 EurekaClientApplication 类,加入线程休眠,模拟服务超时的情况:

 

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_Feign_14

接下来,我们依次重新启动服务:服务提供者(EurekaClientApplication 9000端口、然后修改端口号=9001,再启动,一共2个服务。可以对着启动类鼠标右键 Debug 启动)、Feign服务消费者(FeignConsumerApplication)。

浏览器地址输入:http://main.study.com:10001/feign  会出现如下情况:

①访问访问返回 500,这时候,我们去 eureka-feign 服务的控制台看下错误信息

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_SpringCloud_15

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_Cloud_16

发现 eureka-feign 的控制台出现读取超时的情况,然后我们去到 eureka-client 端口为 9000 和 9001 两个服务的控制台看下信息:

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_微服务架构_17

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_Feign_18

我们发现这 2 个服务线程都超过了我们设置的 1000 毫秒(指定服务的配置),所以出现读取超时的情况。

多次刷新,我们会看到正常的情况:

eureka-client-biandan 说:让天下没有难写的代码!from port =9001

这时候,我们去 eureka-client 端口是 9001 的服务控制台看下信息:

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_Cloud_19

②其中一个服务超时,另一个服务正常的情况。比如端口 9000 的服务线程休眠了 1200 毫秒,这时候 Feign 通过重试机制,去调用端口 9001 的服务,假如 9001 的服务线程只休眠了 50 毫秒,则可以返回正常结果。

所以,重试机制对于高可用的服务集群来说非常重要。

注意:Ribbon 的超时和 Hystrix 的超时熔断是两个不同的概念。Hystrix 的超时直接熔断,也就是返回 fallbackMethod 里我们自定义的返回错误信息的方法。比如我们系列教程代码如下:@HystrixCommand(fallbackMethod = "errorFallBack")

返回的是我们自定义的 errorFallBack() 方法。我们需要让 Hystrix 的超时熔断时间大于

 

Hystrix 配置

服务降级呢?

1、全局配置

Feign 对于 Hystrix 的全局配置同 SpringCloud Ribbon 的全局配置一样,直接使用它的默认配置前缀 hystrix.command.default 就可以进行设置。比如设置全局的超时时间:hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 5000

关闭 Hystrix 熔断功能有两种配置方式,见如下代码:

# 全局 hystrix 配置
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: false #关闭熔断功能
        isolation:
          thread:
            timeoutInMilliseconds: 500 #设置超时熔断时间

# 关闭 Hystrix 熔断功能
feign:
  hystrix:
    enabled: false

 

2、指定命令配置

一般采用 hystrix.command.<commandKey> 作为前缀,而 <commandKey> 默认情况下采用 Feign 客户端中的方法名作为标识。如我们上面的例子对 SayHelloService 接口的 sayHelloFrom() 方法的熔断超时时间配置设置如下:

hystrix:
  command:
    sayHelloFrom:
      isolation:
        thread:
          timeoutInMilliseconds: 8000 #设置超时熔断时间

 

注意:在使用指定命令配置的时候,方法名可能重复,这时候相同的方法的 Hystrix 配置会共用。如果想单独配置,可以重写 Feign.Builder 的实现,并在应用的主类中创建它的实例来覆盖自动化配置的 HystrixFeign.Builder 实现。

 

服务降级配置

 Hystrix 提供的服务降级是服务容错的重要功能,Feign 在定义服务客户端时,HystrixCommand 的定义被封装了起来,我们就不能像之前介绍 Hystrix 时那样通过 @HystrixCommand 注解的 fallback 参数来指定具体的服务降级处理方法。但是,Feign 提供了另外一种更简单的方式。我们来改造一下 eureka-feign 模块。

首先我们在 service 包下创建一个包:impl,然后在 impl 包里创建一个 SayHelloService 的实现类:SayHelloServiceImpl

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_spring_20

 

springcloud微服务 Feign 异步调用 springcloud微服务调用原理_spring_21

SayHelloServiceImpl 完整代码如下:

package com.study.service.impl;

import com.study.service.SayHelloService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author biandan
 * @signature 让天下没有难写的代码
 * @create 2019-10-26 下午 9:23
 */
@Component
public class SayHelloServiceImpl implements SayHelloService {

    @Value("{spring.application.name}")
    private String serverName;

    @Override
    public String sayHelloFrom() {
        return serverName + " 说:请求接口超时。";
    }
}

 说明:服务降级逻辑实现只需要为 Feign 客户端的定义接口编写一个实现类。比如 SayHelloService 接口的一个服务降级类:SayHelloServiceImpl 每个重写的方法都可以用来定义相应的服务降级逻辑

接下来,改造 SayHelloService 接口。通过 @FeignClient 的注解的 fallback 属性来指定对应的服务降级实现类

package com.study.service;

import com.study.service.impl.SayHelloServiceImpl;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author biandan
 * @signature 让天下没有难写的代码
 * @create 2019-10-24 下午 11:29
 */
@FeignClient(value = "eureka-client-biandan",fallback = SayHelloServiceImpl.class)
public interface SayHelloService {

    //调用上述服务的 say() 方法
    @RequestMapping("/say")
    String sayHelloFrom();
}

注意这时候的 application.yml 的完整配置如下,默认熔断的时间是 500 毫秒:

# 这是客户端服务的配置节点
server:
  port: 10001

eureka:
  instance:
    hostname: main.study.com
    lease-renewal-interval-in-seconds: 30
    lease-expiration-duration-in-seconds: 90
  client:
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:8000/eureka/

# 服务的名字
spring:
  application:
    name: eureka-feign-client

# 全局 Ribbon 配置
ribbon:
  ConnectTimeout: 1500
  ReadTimeout: 1500

# 全局 hystrix 配置
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2500 #设置超时熔断时间

# 开启 Hystrix 熔断功能
feign:
  hystrix:
    enabled: true

接下来,我们测试。重启 Feign 服务(FeignConsumerApplication)。然后停掉 eureka-client 两个服务(端口分别是 9000 和 9001 两个服务),来模拟请求超时的情况。浏览器访问地址:http://main.study.com:10001/feign 可以看到如下情况:

eureka-feign-client 说:请求接口超时。

这样一来,我们就实现类服务降级的效果。也就是 Feign 实现了 Hystrix 的熔断功能。