Feign-Hystrix 熔断降级,还在被各种组件欺负?

887 阅读9分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

前言

最近这些年,微服务盛行。服务拆分越来越细、越来越多。为方便微服务之间的调用,各种 RPC 框架层出不穷,目的就是让开发者像调用本地方法一样调用微服务。

微服务之间交流的核心是网络请求,这个过程涉及到应用层和网络层:

  • 网络层:最常用的是 TCP / UDP 协议
  • 应用层:本质上说,应用层协议是负责对数据编码、解码的约定,比如我们常见的 HTTP、Websocket 协议等。

一般情况下,我们想要实现网络通信,有两种选择:

  • 使用现成应用层协议:比如 http 等
  • 基于网络层(TCP/UDP)自己实现应用层协议

两种方式的应用都十分广泛,各有各的好处。本文介绍的 Feign 组件便是直接使用现成的应用层通信协议 http 。

服务间通信伴随着很多问题,比如负载均衡、容错降级等,这些属于基础组件,与业务不相关,可以抽出来作独立存在。

有了基础组件,我们可以按需进行整合。这些整合逻辑有时也可以抽出来公用,比如 feign 与 hystrix 整合后的 Feign-Hystrix,SpringCloud 与 feign 的整合后的 SpringCloud-OpenFeign 等等。

整合的东西越来越多,很容易让人迷惑,你想过如何辨别吗?

破解的关键在于:先了解基础组件的使用方式,自己尝试去做整合。因为原理具有相似性,清楚了基础组件的使用方式,再去看其他整合组件,这个时候往往能看到本质。


一、Feign

1. Feign 是什么?

我们先来看看官方介绍:

在这里插入图片描述

看大标题,Feign 使得用 Java 写 Http 客户端更加容易,这是 Feign 的核心能力!!!

怎么个简单法?

你想想,平时自己写 http 请求是不是需要自己构建请求头、请求体,然后发送请求、解析响应这些重复的过程?

好,现在这个过程不需要你来做了,Feign 将其抽象为一套通用逻辑,类似于写普通方法调用一样,Feign 做一层代理,封装这些冗余操作。

feign 大致提供了这些核心能力:

在这里插入图片描述

2. 如何使用?

maven jar 包依赖:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
    <version>11.9.1</version>
</dependency>

先定义调用接口:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  @RequestLine("POST /repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);

}

public static class Contributor {
  String login;
  int contributions;
}

public static class Issue {
  String title;
  String body;
  List<String> assignees;
  int milestone;
  List<String> labels;
}

再进行调用:

public class MyApp {
  public static void main(String... args) {
    // 构建接口实现,本质是自动包装 http 能力
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder()) // 解码器
                         .target(GitHub.class, "https://api.github.com");

    // 像使用本地方法一样,直接调用(底层是 http 请求)
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

以上是使用 Feign 最原始的方式,你可以进一步抽象,将 构建接口实现 抽象出来,使其成为公用逻辑。

比如通过注解 + 启动扫描的方式,在 Spring Cloud 体系下的 feign 整合便是做了这层处理。

二、Hystrix

1. Hystrix 是什么?

先看看官方介绍:

在这里插入图片描述

你看,hystrix 专注于解决分布式系统延迟和容错问题。换句话说,hystrix 主要解决服务通信间的网络延时、异常等,提供降级和熔断等能力。

还不够通俗?当你在服务 A 调用服务 B 的接口时,可能出现超时、异常的情况,很有可能是服务 B 压力过大,已不堪重负。这时,服务 A 该如何做?

我们可以考虑一些策略,比如,请求失败了,我们返回默认结果;失败率超过多少多少,直接短暂停用接口调用。

Hystrix 便是致力于解决这类问题,实现以上的各种处理策略,熔断、降级等。

遗憾的是,官方已经声明不在维护

在这里插入图片描述

不过,Hystrix 在 Netflix 稳定运行多年,足够稳健,你可以放心在生产环境使用。

2. 如何使用?

hystrix 采用命令模式,每一类请求都需要实现抽象类 HystrixCommand,并实现 run 方法(具体的业务逻辑):

public class CommandHelloWorld extends HystrixCommand<String> {

    private final String name;

    public CommandHelloWorld(String name) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.name = name;
    }

    @Override
    protected String run() {
        // a real example would do work like a network call here
        return "Hello " + name + "!";
    }
}

然后调用:

    @Test
    public void testSynchronous() {
        assertEquals("Hello World!", new CommandHelloWorld("World").execute());
        assertEquals("Hello Bob!", new CommandHelloWorld("Bob").execute());
    }

看到了吧,就是这么简单,只不过在生产上需要结合业务做一些参数调整。

值得注意的是hystrix 可不只是用于 feign,只要有熔断降级的场景,都可以考虑使用

比如:你想给数据库查询做熔断降级,在查询语句外包装一层 hystrix 即可,至于如何包装,可以参考开源的组件,比如 feign-hystrix。

三、Feign-Hystrix

1. 粘合器?

Feign 是一套独立的组件,负责服务间通信;hystrix 也是一套独立的组件,负责服务间通信的熔断降级。

当 feign 也想要熔断降级能力时,直接整合 hystrix 即可。

业界有个不成文的习惯,对于整合其他组件,一般重新定义一个模块,命名为 xxx-xxx,所以你会看到,feign-hystrix 也是这样来的。

我们看看 feign-hystrix 官方定义:

在这里插入图片描述

feign 整合 hystrix 是这样的:直接在 feign 的 http 请求之上包装一层 hystrix,使其拥有熔断降级能力

你也可以这样理解,熔断降级能力是通过统计 http 请求的超时率、错误率来计算并选择处理行为,我们在 feign 的 http 之上包装一层 hystrix 的逻辑,本质就是统计超时、错误等指标,并根据这些指标作出一些反映。

我们看看 feign-hystrix 模块的实现:

在这里插入图片描述

代码量非常少,从实现上看,基本上和你自己使用 hystrix 没啥区别(不过,可能 feign 考虑的更加完善以及通用),主要有:

  • HystrixCommand 创建时需要的 SetterFactory
  • Hystrix 执行降级逻辑的 FallbackFactory
  • 执行 Wrapper 能力的代理类 HystrixInvocationHandler

2. 如何使用?

定义接口:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<String> contributors(@Param("owner") String owner, @Param("repo") String repo);
}

定义 fallback 方法:

// This instance will be invoked if there are errors of any kind.
GitHub fallback = (owner, repo) -> {
  if (owner.equals("Netflix") && repo.equals("feign")) {
    return Arrays.asList("stuarthendren"); // inspired this approach!
  } else {
    return Collections.emptyList();
  }
};

包裹 Http 请求:

GitHub github = HystrixFeign.builder()
                            ...
                            .target(GitHub.class, "https://api.github.com", fallback);

就是这样几个操作,就让你的 feign 调用拥有了熔断降级的能力。

再近一步看,如果你生产上习惯使用 Spring 大家族,那就更加方便了。SpringCloud OpenFeign 内部帮你封装了 Hystrix 包裹 这层,它对外提供注解 @FeignClient,通过扫描该注解,自动帮你完成这些动作。

四、SpringCloud OpenFeign

1. 新物种?

Spring 是一个大家族,从内需(IOC、AOP ...)到组件整合(Feign、Ribbon ...),给开发者带来了极大的便利。

Spring 的终极使命是,让配置尽可能简洁、让开发者尽可能专注于业务,到了 SpringBoot 时代,已经有了质的提升。

对于一些优秀开源的外部组件,Spring 也想要纳入麾下,希望像使用自身组件一样使用外部组件,这就是 Spring 组件整合之路。

整合是好事,但可能让人越来越懵。为啥?比如与 feign 相关常见的组件有:Feign、Feign-Hystrix、SpringCloud-OpenFeign ....

多了之后,你可能就会疑惑,这些组件之间到底都是啥关系?耐心的拨开这神秘的面纱,你就能搂到底浆!!!

SpringCloud-OpenFeign 本质也是将 SpringFeign进行整合,底层能力仍然由 Feign 提供。如果 Feign 需要熔断降级能力,就将 Feign-Hystrix 整合组件依赖进来;如果需要负载均衡,就将 Ribbon 整合进来。

看到了吧,Feign 本身是解决 Http 通信问题,这过程要考虑 负载均衡、熔断降级 等等,而这些问题有现成的优秀组件,直接组合在一起就可以了,这就是 SpringCloud-OpenFeign,看起来像粘合剂。

2. 如何使用?

引入 jar 包:

implementation("org.springframework.cloud:spring-cloud-starter-openfeign:2.2.7.RELEASE")

启动 Feign:

@SpringBootApplication
@EnableFeignClients
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

定义 FeignClient:

@FeignClient("stores")
public interface StoreClient {
    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    List<Store> getStores();

    @RequestMapping(method = RequestMethod.GET, value = "/stores")
    Page<Store> getStores(Pageable pageable);

    @RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
    Store update(@PathVariable("storeId") Long storeId, Store store);
}

最小可用版的 SpringClound OpenFeign 案例算是完成了。在 Spring 体系下,StoreClient 就是一个 bean,直接调用方法即可。

当然,如果你想使用熔断降级能力,那就打开相应配置:

feign.hystrix.enabled=true

然后,我们再定义降级方法 fallback:

@Component
static class HystrixClientFallbackFactory implements FallbackFactory<HystrixClient> {
    @Override
    public HystrixClient create(Throwable cause) {
        return new HystrixClient() {
            @Override
            public Hello iFailSometimes() {
                return new Hello("fallback; reason was: " + cause.getMessage());
            }
        };
    }
}

最终展现是这样:

@FeignClient(name = "hello", fallbackFactory = HystrixClientFallbackFactory.class)
protected interface HystrixClient {
    @RequestMapping(method = RequestMethod.GET, value = "/hello")
    Hello iFailSometimes();
}

就这几个步骤,让你的 feign 调用拥有熔断降级能力!


最后提一下,软件在不断升级,在 SpringClound-OpenFeign 的 3.x.x 版本已经将整合组件 feign-hystrix 剔除依赖。

因为 SpringClound-OpenFeign 自身对熔断降级做了一层抽象,并自己写 hystrix 的整合组件,同时也支持其他的熔断降级组件,比如 resilience4j

总结

本文致力于讲述组件的核心能力、功能边界、整合方式和基本的使用

feign 和 hystrix 作为基础组件,分别解决 http 和 熔断降级问题。通常情况下,这两种会结合使用,考虑到这层因素,feign 提供了子模块 feign-hystrix 去整合两个组件。

多数情况下,我们不需要自己去做整合,引入 feign-hytrix 模块即可,早期的 springcloud-openfeign 也是如此。

市场上开源的熔断降级组件也不止 hystrix,Springcloud-openfeign 从 2.2.7.RELEASE 版本开始抽象熔断降级能力,并逐渐剔除 feign-hystrix 依赖,转而提供自身的抽象实现(整合):

  • spring-cloud-starter-netflix-hystrix
  • spring-cloud-starter-circuitbreaker-resilience4j
  • ...

花样越来越多,学会抓住问题的本质!




相关参考: