OpenFeign 实战:OpenFeign 组件有哪些高级玩法?

OpenFeign 实战:OpenFeign 组件有哪些高级玩法?

今天我们来进一步深入 OpenFeign 的功能特性,学习几个 OpenFeign 的进阶使用技巧:异常信息排查、超时判定和服务降级。

异常信息排查是我们开发人员每天都要面对的事情。如果你正在开发一个大型微服务应用,你经常需要集成一些由其他团队开发的 API,这就免不了要参与各种联调和问题排查。如果你是一个经验丰富的老码农,那你一定经常说这样一句话:“你的 Request 参数是什么?”这句台词在我们平时的 API 联调和线上异常排查中出镜率很高,因为服务请求的入参和出参是分析和排查问题的重要线索。

为了获得服务请求的参数和返回值,我们经常使用的一个做法就是打印日志。你可以在程序中使用 log.info 或者 log.debug 方法将服务请求的入参和返回值一一打印出来。但是,对一些复杂的业务场景来说就没有那么轻松了。

假如你在开发的是一个下单服务,执行一次下单流程前前后后要调用十多个微服务。你需要在请求发送的前后分别打印 Request 和 Response,不仅麻烦不说,我们还未必能把包括 Header 在内的完整请求信息打印出来。

那我们如何才能引入一个既简单又不需要硬编码的日志打印功能,让它自动打印所有远程方法的 Request 和 Response,方便我们做异常信息排查呢?

接下来,我就来给你介绍一个 OpenFeign 的小功能,轻松实现远程调用参数的日志打印。

日志信息打印

为了让 OpenFeign 可以主动将请求参数打印到日志中,我们需要做两个代码层面的改动。首先,你需要在配置文件中指定 FeignClient 接口的日志级别为 Debug。这样做是因为 OpenFeign 组件默认将日志信息以 debug 模式输出,而默认情况下 Spring Boot 的日志级别是 Info,因此我们必须将应用日志的打印级别改为 debug 后才能看到 OpenFeign 的日志。

我们打开 coupon-customer-impl 模块的 application.yml 配置文件,在其中加上以下几行 logging 配置项。


logging:
  level:
    com.geekbang.coupon.customer.feign.TemplateService: debug
    com.geekbang.coupon.customer.feign.CalculationService: debug

在上面的配置项中,我指定了 TemplateService 和 CalculationService 的日志级别为 debug,而其它类的日志级别不变,仍然是默认的 Info 级别。

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


@Bean
Logger.Level feignLogger() {
    return Logger.Level.FULL;
}

在上面这段代码中,我指定了 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-calculation-serv/calculator/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)

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

到这里,我们就详细了解了 OpenFeign 的日志级别设置。接下来,我带你了解如何在 OpenFeign 中配置超时判定条件。

OpenFeign 超时判定

超时判定是一种保障可用性的手段。如果你要调用的目标服务的 RT(Response Time)值非常高,那么你的调用请求也会处于一个长时间挂起的状态,这是造成服务雪崩的一个重要因素。为了隔离下游接口调用超时所带来的的影响,我们可以在程序中设置一个超时判定的阈值,一旦下游接口的响应时间超过了这个阈值,那么程序会自动取消此次调用并返回一个异常。

我们以 coupon-customer-serv 为例,customer 服务依赖 template 服务来读取优惠券模板的信息,如果你想要对 template 的远程服务调用添加超时判定配置,那么我们可以在 coupon-customer-impl 模块下的 application.yml 文件中添加下面的配置项。


feign:
  client:
    config:
      # 全局超时配置
      default:
        # 网络连接阶段1秒超时
        connectTimeout: 1000
        # 服务请求响应阶段5秒超时
        readTimeout: 5000
      # 针对某个特定服务的超时配置
      coupon-template-serv:
        connectTimeout: 1000
        readTimeout: 2000

从上面这段代码中可以看出,所有超时配置都放在 feign.client.config 路径之下,我在这个路径下面声明了两个节点:default 和 coupon-template-serv。

default 节点配置了全局层面的超时判定规则,它的生效范围是所有 OpenFeign 发起的远程调用。

coupon-template-serv 下面配置的超时规则只针对向 template 服务发起的远程调用。如果你想要对某个特定服务配置单独的超时判定规则,那么可以用同样的方法,在 feign.client.config 下添加目标服务名称和超时判定规则。

这里需要你注意的一点是,如果你同时配置了全局超时规则和针对某个特定服务的超时规则,那么后者的配置会覆盖全局配置,并且优先生效。在超时判定的规则中我定义了两个属性:connectTimeout 和 readTimeout。

  • connectTimeout 的超时判定作用于“建立网络连接”的阶段;
  • readTimeout 的超时判定则作用于“服务请求响应”的阶段(在网络连接建立之后)。

我们常说的 RT(即服务响应时间)受后者影响比较大。另外,这两个属性对应的超时时间单位都是毫秒。

配置好超时规则之后,我们可以验证一下。你可以在 template 服务中使用 Thread.sleep 方法强行让线程挂起几秒钟,制造一个超时场景。这时如果你通过 customer 服务调用了 template 服务,那么在日志中可以看到下面的报错信息,提示你服务请求超时。


[TemplateService#getTemplate] <--- ERROR SocketTimeoutException: Read timed out (2077ms)
[TemplateService#getTemplate] java.net.SocketTimeoutException: Read timed out

OpenFeign 降级降级逻辑是在远程服务调用发生超时或者异常(比如 400、500 Error Code)的时候,自动执行的一段业务逻辑。你可以根据具体的业务需要编写降级逻辑,比如执行一段兜底逻辑将服务请求从失败状态中恢复,或者发送一个失败通知到相关团队提醒它们来线上排查问题。

在后面文章中,我将会使用 Spring Cloud Alibaba 的组件 Sentinel 跟你讲解如何搭建中心化的服务容错控制逻辑,这是一种重量级的服务容错手段。

但在这篇文章中,我采用了一种完全不同的服务容错手段,那就是借助 OpenFeign 实现 Client 端的服务降级。尽管它的功能远不如 Sentinel 强大,但它相比于 Sentinel 而言更加轻量级且容易实现,足以满足一些简单的服务降级业务需求。

OpenFeign 对服务降级的支持是借助 Hystrix 组件实现的,由于 Hystrix 已经从 Spring Cloud 组件库中被移除,所以我们需要在 coupon-customer-impl 子模块的 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>

添加好依赖项之后,我们就可以编写 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;
    }

    @Override
    public Map<Long, CouponTemplateInfo> getTemplateInBatch(Collection<Long> ids) {
        log.info("fallback getTemplateInBatch");
        return null;
    }
}

在上面的代码中,我们可以看出 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 {
      // ... 省略方法定义
}

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


@Slf4j
@Component
public class TemplateServiceFallbackFactory implements FallbackFactory<TemplateService> {

    @Override
    public TemplateService create(Throwable cause) {
        // 使用这种方法你可以捕捉到具体的异常cause
        return new TemplateService() {

            @Override
            public CouponTemplateInfo getTemplate(Long id) {
                log.info("fallback factory method test");
                return null;
            }

            @Override
            public Map<Long, CouponTemplateInfo> getTemplateInBatch(Collection<Long> ids) {
                log.info("fallback factory method test");
                return Maps.newHashMap();
            }
        };
    }
}

从上面的代码中,你可以看出,抽象工厂 create 方法的入参是一个 Throwable 对象。这样一来,我们在降级方法中就可以获取到原始请求的具体报错异常信息了。

当然了,你还需要将这个工厂类添加到 TemplateService 注解中,这个过程和指定 fallback 类的过程有一点不一样,你需要借助 FeignClient 注解的 fallbackFactory 属性来完成。你可以参考下面的代码。


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

到这里,我们就完成了 OpenFeign 进阶功能的了解。针对这里面的某些功能,我想从日志打印和超时判定这两个方面给你一些实践层面的建议。

在日志打印方面,OpenFeign 的日志信息是测试开发联调过程中的好帮手,但是在生产环境中你是用不上的,因为几乎所有公司的生产环境都不会使用 Debug 级别的日志,最多是 Info 级别。

在超时判定方面,有时候我们在线上会使用多维度的超时判定,比如 OpenFeign + 网关层超时判定 + Sentinel 等等判定。它们可以互相作为兜底方案,一旦某个环节突然发生故障,另一个可以顶上去。但这就形成了一个木桶理论,也就是几种判定规则中最严格的那个规则会优先生效。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值