• Feign-Hystrix 熔断降级,一文看透本质



    前言

    最近这些年,微服务盛行。服务拆分越来越细、越来越多。为方便微服务之间的调用,各种 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>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    先定义调用接口:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    再进行调用:

    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 + ")");
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    以上是使用 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 + "!";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    然后调用:

        @Test
        public void testSynchronous() {
            assertEquals("Hello World!", new CommandHelloWorld("World").execute());
            assertEquals("Hello Bob!", new CommandHelloWorld("Bob").execute());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    值得注意的是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);
    }
    
    • 1
    • 2
    • 3
    • 4

    定义 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();
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    包裹 Http 请求:

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

    就是这样几个操作,就让你的 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")
    
    • 1

    启动 Feign:

    @SpringBootApplication
    @EnableFeignClients
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    定义 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

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

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

    feign.hystrix.enabled=true
    
    • 1

    然后,我们再定义降级方法 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());
                }
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    最终展现是这样:

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

    就这几个步骤,让你的 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

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




    相关参考:

  • 相关阅读:
    vue-cli 初始----安装运行Vue项目
    约数:AcWing 870. 约数个数
    热烈祝贺|天栩(广东)有限公司受邀参加2022世界滋补产业生态大会
    最近身边一个技术负责人裸辞了...
    保护视力,从 CareUEyes 开始 —— 你的电脑护眼小助手
    【IoT】产品认证:国密认证中的委托人、生产者、生产企业是什么意思?
    C++构造函数
    C++中的继承
    2374. 边积分最高的节点
    数据分析三剑客-Matplotlib
  • 原文地址:https://blog.csdn.net/ldw201510803006/article/details/126414836