当前位置:   article > 正文

使用 OpenFeign 实现跨服务的调用_openfeign实现服务与服务之间相互调用

openfeign实现服务与服务之间相互调用

使用OpenFeign进行服务间调用

本次使用 OpenFeign 的业务场景为:用户下单前领取产品优惠券,使用优惠券再下单。
架构设计:用户服务(CustomerService)和优惠券管理服务(CouponTemplateService)分别是平台的独立子服务。
使用OpenFeign在用户服务里读取优惠券信息的步骤为:

  1. 把它的依赖项 spring-cloud-starter-OpenFeign 添加到用户模块(CustomerService)内的 pom.xml 文件中。
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4

PS. 在上面的代码中,不需要指定组件的版本号,因为在顶层项目中定义的 spring-cloud-dependencies 依赖项中已经定义了各个 Spring Cloud 的版本号,它们会随着 Maven 项目的继承关系传递到子模块中。

  1. 定义一个 OpenFeign 接口

OpenFeign 组件通过接口代理的方式发起远程调用,那么使用过程的第一步就是要定义一个 OpenFeign 接口。
在 用户服务(CustomerService)项目下创建了一个 package,它的路径是 com.domo.customer.feign。在这个路径下定义一个叫做 TemplateService 的 Interface,用来实现对 coupon-template-service的远程调用代理。

@FeignClient(value = "coupon-template-service", path = "/template")
public interface TemplateService {
    // 读取优惠券
    @GetMapping("/getTemplate")
    CouponTemplateInfo getTemplate(@RequestParam("id") Long id);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在上面的代码中,在接口上声明了一个 FeignClient 注解,它专门用来标记被 OpenFeign 托管的接口。
在 FeignClient 注解中声明的 value 属性是目标服务的名称,需要确保这里的服务名称和 Nacos 上显示的服务注册名称是一样的。
此外,FeignClient 注解中的 path 属性是一个可选项,如果要调用的目标服务有一个统一的前置访问路径,那么可以通过 path 属性来声明这个前置路径,这样一来,就不用在每一个方法名上的注解中带上前置 Path 了。
在项目的启动阶段,OpenFeign 会查找所有被 FeignClient 注解修饰的接口,并代理该接口的所有方法调用。当业务方法中调用接口方法的时候,OpenFeign 就会根据方法上定义的注解自动拼装 HTTP 请求路径和参数,并向目标服务发起真实调用。
因此,还需要在方法上定义 spring-web 注解(如 GetMapping、PostMapping),让 OpenFeign 拼装出正确的 Request URL 和请求参数。这里要注意,OpenFeign 接口中定义的路径和参数必须与你要调用的目标服务中的保持一致。

  1. 接下来就可以替换 CustomerServiceImpl 中的业务逻辑调用了。
    首先,在CustomerServiceImpl 接口中注入刚才定义的 TemplateService 接口。然后,使用 OpenFeign 接口发起远程调用。可以看出OpenFeign 使用就像使用本地服务一样简单。OpenFeign 组件不光可以提高代码可读性和可维护性,还降低了远程调用的 Coding 成本。
@Service
public class CustomerServiceImpl implements CustomerService {
    @Autowired
    private TemplateService templateService;

    @Override
    public CouponTemplateInfo getTemplate(Long templateId){
        CouponTemplateInfo templateInfo = templateService.getTemplate(templateId);
        return templateInfo;
    }
}   

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  1. 配置 OpenFeign 的加载路径
    打开customer-service 项目的启动类,通过在类名之上添加一个 EnableFeignClients 注解的方式定义 OpenFeign 接口的加载路径。
// 省略其他无关注解
@EnableFeignClients(basePackages = {"com.demo"})
public class CustomerServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(CustomerServiceApplication .class, args);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这段代码中,在 EnableFeignClients 注解的 basePackages 属性中定义了一个 com.demo的包名,这个注解就会告诉 OpenFeign 在启动项目的时候,找到所有位于 com.demo包路径(包括子 package)之下使用 FeignClient 修饰的接口,然后生成相关的代理类并添加到 Spring 的上下文中。这样一来,才能够在项目中用 Autowired 注解注入 OpenFeign 接口。
扩展:
上面就是使用包路径扫描的方式来加载 FeignClient 接口。除此之外,还可以通过直接加载指定 FeignClient 接口类的方式,或者从指定类所在的目录进行扫包的方式来加载 FeignClient 接口。

// 通过指定Client类来加载
@EnableFeignClients(clients = {TemplateService.class})

// 扫描特定类所在的包路径下的FeignClient
@EnableFeignClients(basePackageClasses = {TemplateService.class})
  • 1
  • 2
  • 3
  • 4
  • 5

这三种加载方式,项目中推荐使用的是包路径加载的方式。因为不管以后添加了多少新的 FeignClient 接口,只要这些接口位于指定包路径之下,就不用操心加载路径的配置。

OpenFeign 的进阶使用技巧

异常信息排查-日志信息打印

服务请求的入参和出参是分析和排查问题的重要线索。为了获得服务请求的参数和返回值,经常使用的一个做法就是打印日志。
为了让 OpenFeign 可以主动将请求参数打印到日志中,需要做两个代码层面的改动。
首先,需要在配置文件中指定 FeignClient 接口的日志级别为 Debug。这样做是因为 OpenFeign 组件默认将日志信息以 debug 模式输出,而默认情况下 Spring Boot 的日志级别是 Info,因此我们必须将应用日志的打印级别改为 debug 后才能看到 OpenFeign 的日志。
打开 customer 模块的 application.yml 配置文件,在其中加上以下几行 logging 配置项

logging:
  level:
    com.demo.customer.feign.TemplateService: debug
  • 1
  • 2
  • 3

在上面的配置项中,指定了 TemplateService 的日志级别为 debug,而其它类的日志级别不变,仍然是默认的 Info 级别。
接下来,还需要在应用的上下文中使用代码的方式声明 Feign 组件的日志级别。这里的日志级别并不是我们传统意义上的 Log Level,它是 OpenFeign 组件自定义的一种日志级别,用来控制 OpenFeign 组件向日志中写入什么内容。可以打开customer 模块的 Configuration 配置类,在其中添加这样一段代码。


@Bean
Logger.Level feignLogger() {
    return Logger.Level.FULL;
}
  • 1
  • 2
  • 3
  • 4
  • 5

在上面这段代码中,指定了 OpenFeign 的日志级别为 Full,在这个级别下所输出的日志文件将会包含最详细的服务调用信息。OpenFeign 总共有四种不同的日志级别,下面是这四种级别下 OpenFeign 向日志中写入的内容。

  • NONE:不记录任何信息,这是 OpenFeign 默认的日志级别;
  • BASIC:只记录服务请求的 URL、HTTP Method、响应状态码(如 200、404 等)和服务调用的执行时间;
  • HEADERS:在 BASIC 的基础上,还记录了请求和响应中的 HTTP Headers;
  • FULL:在 HEADERS 级别的基础上,还记录了服务请求和服务响应中的 Body 和 metadata,FULL 级别记录了最完整的调用信息。

将 Feign 的日志级别指定为 Full,并启动项目发起一个远程调用,就可以在日志中看到整个调用请求的信息,包括请求路径、Header 参数、Request Payload 和 Response Body。调用日志信息如下:


 ---> POST http://coupon-template-serv/template/simulate HTTP/1.1
 Content-Length: 458
 Content-Type: application/json
 
 {"products":[{"productId":null,"price":3000, xxxx省略请求参数
 ---> END HTTP (458-byte body)
 <--- HTTP/1.1 200 (29ms)
 connection: keep-alive
 content-type: application/json
 date: Sat, 27 Nov 2021 15:11:26 GMT
 keep-alive: timeout=60
 transfer-encoding: chunked
 
 {"bestCouponId":26,"couponToOrderPrice":{"26":15000}}
 <--- END HTTP (53-byte body)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

有了这些详细的日志信息,在开发联调阶段排查异常问题就易如反掌了。

OpenFeign 超时判定

超时判定是一种保障可用性的手段。如果要调用的目标服务的 RT(Response Time)值非常高,那么调用请求也会处于一个长时间挂起的状态,这是造成服务雪崩的一个重要因素。为了隔离下游接口调用超时所带来的的影响,可以在程序中设置一个超时判定的阈值,一旦下游接口的响应时间超过了这个阈值,那么程序会自动取消此次调用并返回一个异常。
例如上述场景,customer 服务依赖 template 服务来读取优惠券模板的信息,如果想要对 template 的远程服务调用添加超时判定配置,那么我们可以在 customer 模块下的 application.yml 文件中添加下面的配置项。

feign:
  client:
    config:
      # 全局超时配置
      default:
        # 网络连接阶段1秒超时
        connectTimeout: 1000
        # 服务请求响应阶段5秒超时
        readTimeout: 5000
      # 针对某个特定服务的超时配置
      coupon-template-service:
        connectTimeout: 1000
        readTimeout: 2000
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

从上面这段代码中可以看出,所有超时配置都放在 feign.client.config 路径之下,在这个路径下面声明了两个节点:default 和 coupon-template-service。
default 节点配置了全局层面的超时判定规则,它的生效范围是所有 OpenFeign 发起的远程调用。
coupon-template-service下面配置的超时规则只针对向 template 服务发起的远程调用。如果想要对某个特定服务配置单独的超时判定规则,那么可以用同样的方法,在 feign.client.config 下添加目标服务名称和超时判定规则。
如果同时配置了全局超时规则和针对某个特定服务的超时规则,那么后者的配置会覆盖全局配置,并且优先生效。
在超时判定的规则中定义了两个属性:connectTimeout 和 readTimeout。
其中,connectTimeout 的超时判定作用于“建立网络连接”的阶段;而 readTimeout 的超时判定则作用于“服务请求响应”的阶段(在网络连接建立之后)。我们常说的 RT(即服务响应时间)受后者影响比较大。另外,这两个属性对应的超时时间单位都是毫秒。

OpenFeign 降级

降级逻辑是在远程服务调用发生超时或者异常(比如 400、500 Error Code)的时候,自动执行的一段业务逻辑。可以根据具体的业务需要编写降级逻辑,比如执行一段兜底逻辑将服务请求从失败状态中恢复,或者发送一个失败通知到相关团队提醒它们来线上排查问题。
这里借助 OpenFeign 实现 Client 端的服务降级。尽管它的功能远不如 Sentinel 强大,但它相比于 Sentinel 而言更加轻量级且容易实现,足以满足一些简单的服务降级业务需求。
OpenFeign 对服务降级的支持是借助 Hystrix 组件实现的,由于 Hystrix 已经从 Spring Cloud 组件库中被移除,所以我们需要在 customer模块的 pom 文件中手动添加 hystrix 项目的依赖。

<!-- hystrix组件,专门用来演示OpenFeign降级 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    <version>2.2.10.RELEASE</version>
    <exclusions>
        <!-- 移除Ribbon负载均衡器,避免冲突 -->
        <exclusion>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-netflix-ribbon</artifactId>
        </exclusion>
    </exclusions>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

添加好依赖项之后,就可以编写 OpenFeign 的降级类了。
OpenFeign 支持两种不同的方式来指定降级逻辑,一种是定义 fallback 类,另一种是定义 fallback 工厂。

通过 fallback 类实现降级

这是最为简单的一种途径,如果想要为 TemplateService 这个 FeignClient 接口指定一段降级流程,那么可以定义一个降级类并实现 TemplateService 接口。如下TemplateServiceFallback 类


@Slf4j
@Component
public class TemplateServiceFallback implements TemplateService {

    @Override
    public CouponTemplateInfo getTemplate(Long id) {
        log.info("fallback getTemplate");
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在上面的代码中, TemplateServiceFallback 实现了 TemplateService 中的所有方法。
例如getTemplate 方法,如果在实际的方法调用过程中,OpenFeign 接口的 getTemplate 远程调用发生了异常或者超时的情况,那么 OpenFeign 会主动执行对应的降级方法,也就是 TemplateServiceFallback 类中的 getTemplate 方法。这样就可以根据具体的业务场景,编写合适的降级逻辑。
降级类定义好之后,还需要在 TemplateService 接口中将 TemplateServiceFallback 类指定为降级类,这里可以借助 FeignClient 接口的 fallback 属性来配置,如下代码:


@FeignClient(value = "coupon-template-serv", path = "/template",
       // 通过fallback指定降级逻辑
       fallback = TemplateServiceFallback.class)
public interface TemplateService {
      // ... 省略方法定义
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
fallback 工厂的方式实现降级

如果想要在降级方法中获取到异常的具体原因,那么就要借助 fallback 工厂的方式来指定降级逻辑了。按照 OpenFeign 的规范,自定义的 fallback 工厂需要实现 FallbackFactory 接口。如下TemplateServiceFallbackFactory 类:


@Slf4j
@Component
public class TemplateServiceFallbackFactory implements FallbackFactory<TemplateService> {
    @Override
    public TemplateService create(Throwable cause) {
       TemplateServiceFallback templateServiceFallback = new TemplateServiceFallback ();
       templateServiceFallback.setCause(throwable);
	   return templateServiceFallback;
    }
}

@Slf4j
@Component
public class TemplateServiceFallback implements TemplateService {
    @Setter
    private Throwable cause;
    
    @Override
    public CouponTemplateInfo getTemplate(Long id) {
        log.info("fallback getTemplate{}", cause);
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

从上面的代码中可以看出,抽象工厂 create 方法的入参是一个 Throwable 对象。这样一来,在降级方法中就可以获取到原始请求的具体报错异常信息了。
最后还需要将这个工厂类添加到 TemplateService 注解中,这个过程和指定 fallback 类的过程有一点不一样,需要借助 FeignClient 注解的 fallbackFactory 属性来完成。


@FeignClient(value = "coupon-template-serv", path = "/template",
        // 通过抽象工厂来定义降级逻辑
        fallbackFactory = TemplateServiceFallbackFactory.class)
public interface TemplateService {
        // ... 省略方法定义
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

实践注意点:
在日志打印方面,OpenFeign 的日志信息是测试开发联调过程中的好帮手,但是在生产环境中是用不上的,因为几乎所有公司的生产环境都不会使用 Debug 级别的日志,最多是 Info 级别。
在超时判定方面,有时候在线上会使用多维度的超时判定,比如 OpenFeign + 网关层超时判定 + Sentinel 等等判定。它们可以互相作为兜底方案,一旦某个环节突然发生故障,另一个可以顶上去。但这就形成了一个木桶理论,也就是几种判定规则中最严格的那个规则会优先生效。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/84523
推荐阅读
相关标签
  

闽ICP备14008679号