OpenFeign 的超时重试机制以及底层实现原理

news2024/11/26 20:21:08

目录

1. 什么是 OpenFeign?

2. OpenFeign 的功能升级

3. OpenFeign 内置的超时重试机制

3.1 配置超时重试

3.2 覆盖 Retryer 对象

4. 自定义超时重试机制

4.1 为什么需要自定义超时重试机制

4.2 如何自定义超时重试机制

5. OpenFeign 超时重试的底层原理

5.1 超时重试原理


1. 什么是 OpenFeign?

        OpenFeign 是一款基于 Feign 的声明式的 Web 服务客户端,它使得编写Web服务客户端变得更加容易。它可以帮助你轻松调用远程服务的工具。

【举个例子】

        想象一下你在使用手机的微信和朋友聊天,你只需要知道朋友的微信号就可以给他发消息,不需要知道他的手机号或者其他复杂的信息。OpenFeign 也是这样,它允许你在编写代码时,只需要知道你想调用的服务的名字和需要交互的部分(比如服务中的某个功能或接口),你不需要处理底层的网络连接或者复杂的HTTP请求过程,这些都由OpenFeign自动帮你完成。它提供了一种类似于调用本地方法的感觉来访问远程服务,从而让开发者可以专注于编写业务代码,而不是底层的网络通信细节。

2. OpenFeign 的功能升级

OpenFeign 在 Feign 的基础上还提供了增强、扩展功能:

  1. 更好的集成SpringCloud组件

    • OpenFeign与Spring Cloud的其他组件(如服务发现、负载均衡)紧密集成,它能够自动利用服务发现和负载均衡的功能,无需额外配置。
  2. 支持@FeignClient注解

    • OpenFeign引入了@FeignClient注解,使得声明式的客户端创建变得简单。你只需要在接口上使用@FeignClient指定服务名即可,而无需创建具体的RestTemplate或者使用URL硬编码远程服务调用。
  3. 错误处理改进

    • OpenFeign提供了更为人性化的错误处理方式。它允许你通过自定义错误解码器来对特定的错误响应进行处理。这样你可以捕捉并处理远程服务调用中的异常,使得异常管理更加灵活和精确。(报错信息也更加细化、明确了)
  4. 更丰富的配置项

    • OpenFeign提供了比原生Feign更为详尽的配置项,比如超时设置、重试策略。

3. OpenFeign 内置的超时重试机制

        在微服务架构中,服务之间是通过网络进行通信的,而网络又是非常复杂和不稳定的,所以在服务调用的过程中可能会失败或超时,那么在这种情况下,OpenFeign 就需要超时重试机制来解决了。

什么是超时重试 ?

        当你的服务请求因为网络问题或服务延迟等原因没能在预定时间内得到响应,超市重试机制会帮你自动重新发送请求。就像你用浏览器打开网页,如果一时加载不出来,你可能会点击刷新重试。OpenFeign 的超时重试就是自动帮你做这件事,尝试几次后如果还是不行,就会告诉你请求失败。这样可以提高服务的可靠性,防止因为网络不稳定、服务不可用、响应延迟等不确定性因素导致服务不可用。

       OpenFeign 默认情况下是不会自动开启超时重试机制的,所以想要使用超时重试功能,需要手动配置:

  1. 配置超时重试
  2. 覆盖 Retryer 对象

        后续的操作都是基于上篇博客中的案例进行演示,在 SpringBoot 整合 Nacos 的案例中,已经涵盖了 OpenFeign 的基础使用了,不会的可以先去看看:https://blog.csdn.net/xaiobit_hl/article/details/134142521

3.1 配置超时重试

spring:
  application:
    name: nacos-consumer-demo
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        username: nacos
        password: nacos
        register-enabled: false  #消费者(不需要将此服务注册到 nacos)
    openfeign:
      client:
        config:
          default: # 全局配置
            connect-timeout: 1000  # 连接超时时间(毫秒)
            read-timeout: 1000  # 读取的超时时间(毫秒)

3.2 覆盖 Retryer 对象

@Configuration  // 存储 Ioc
public class RetryerConfig {
    @Bean
    public Retryer retryer() {
        return new Retryer.Default(
                1000,  // 重试间隔时间
                1000,  // 最大重试间隔时间
                3  // 最大重试次数
        );
    }
}

PS:设置的最大重试次数为 3 次,最大重试间隔时间为 1s,重试间隔时间是 1s。

修改服务提供者代码 >>

        配置信息里边设置的读取超时时间为 1 秒,就是为了触发超时重试机制,按道理来说,这个时候我们的消费者就需要在接口里边 sleep 1秒,为了更好的看见问题,可以设置 1.5 秒:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private ServletWebServerApplicationContext context;

    // 服务
    @RequestMapping("/getnamebyid")
    public String getNameById(Integer id) throws InterruptedException {
        System.out.println("------- do provider getNameById method" +
                LocalDateTime.now());
        Thread.sleep(1500); // 休眠 1.5 s
        return "provider-name-" + id +
                " | port:" + context.getWebServer().getPort();
    }
}

PS:① 启动一个临时服务实例,② 保护阈值设为 0 ,③ 启动消费者。

如果有多个实例,客户端默认的负载均衡是轮询,会导致现象并不明显; 如果有保护阈值,也会导致现象更复杂,所以搞一个服务实例,保护阈值不要去设置。

使用 http://localhost:8080/getnamebyid?id=2 获取服务:

此时服务已经获取不到了, 并且已经触发超时重试机制了,打开服务提供者的控制台:

总共打印了三次日志,因为我们设置的最大重试次数就是 3。

为什么不是  4 次呢?为什么日志的时间间隔是 2s 打印一次呢 ?

① 为什么不是打印 4 次 ?

因为 Retryer 的 Default 方法源码中重试次数变量 attempt 是从 1 开始的,然后核心方法 continueOrPropagate 中的 if 判断是当 attemp >= maxAttempts 时,才抛出异常。

下标从 1 开始 :

 if 判断:

② 为什么日志的时间间隔是 2s 打印一次呢 ? 

我们设置的连接超时时间是 1s,此处肯定是能连的上,所以跟 connect-timeout 没关系。

此处是因为我们设置的读取超时时间(read-timeout)是 1s,并且我们设置了一个间隔时间,也是 1s,所以每次打印日志的间隔时间就是 2s。

4. 自定义超时重试机制

4.1 为什么需要自定义超时重试机制

        因为 OPenFeign 内置的超时重试机制,它的重试策略是固定次数的重试,而这种策略在某些场景下效果并不理想。例如我们设置的重试次数为 3,此时因为网络短暂抖动造成了服务调用失败,而固定策略可能在网络恢复前就已经用完了所有的重试次数,这样就导致重试机制的作用不大。有时候我们更需要的是指数增长的重试策略,就像 TCP 的超时重传一样,那种指数增长的重试策略更加的智能,它会在每次重试失败后增加等待时间,给网络或者服务更多的恢复时间,并减少因短时间内多次重试对服务的潜在影响。

4.2 如何自定义超时重试机制

  1. 自定义超时重试类(实现 Retryer 接口,并重写 continueOrPropagate 方法)
  2. 设置配置文件

① 自定义超时重试类(指数增长)

/**
 * 自定义超时重试类
 */
public class CustomRetryer implements Retryer {
    private final int maxAttempts;  // 最大尝试次数
    private final long backoff;  // 重试间隔时间
    int attempt; // 当前重试次数

    public CustomRetryer() {
        this.maxAttempts = 3;
        this.backoff = 1000L;
        this.attempt = 0;
    }
    @Override
    public void continueOrPropagate(RetryableException e) {
        if(attempt++ >= maxAttempts) {
            throw e;
        }
        long interval = this.backoff;
        System.out.println(LocalDateTime.now() + " | 执行一次重试: " + interval);
        try {
            Thread.sleep(interval * attempt); // 指数增长
        } catch (InterruptedException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public Retryer clone() {
        return new CustomRetryer();
    }
}

② 设置配置文件

retryer 后面写上自定义超时重试类的包名+ 类名。

启动服务实例和消费者,并尝试获取服务,报错后,再查看控制台:

1. 此处我设置了最大重传次数为 3 打印 4 次日志,是因为我自定义的重试类的 attempt 变量从 0 开始的。

2. 观察日志与日志间的时间间隔:从 2s -> 3s -> 4s,最初 attempt 为 1,1*1 + read-timeout 的 1s 所以就是 2s,然后再试 1*2 + read-timeout,以此类推...

5. OpenFeign 超时重试的底层原理

想要搞懂 OpenFeign 超时重试的底层原理,就得先搞清楚 OpenFeign 的底层实现:

① 加注解

        在启动类或者配置类上添加 @EnableFeignClients注解
② 动态代理

        这个注解会触发Spring框架的自动配置机制,扫描所有标记有@FeignClient的接口,并为它们创建代理实例

③ RequestTemplate 发送HTTP请求

        此处的 RequeustTemplate 我们可以理解为 RestTemplate,因为他俩的目的相同。OpenFeign 不能直接发送 HTTP 请求,它在动态代理里面做了一件事,它将注解里面请求的路由地址拿出来,然后就能拼出来一个 URL 请求的地址,然后再使用 RequestTemplate(RestTemplate)去发送 HTTP 请求。

④ RestTemplate 依靠 HTTP 框架实现 web 请求 (把它理解为 RestTemplate)

        RestTemplate 只是一个模板方法类,它只是规定了一个调用的 API,它底层并没有实现,它依靠的是 HTTP 框架实现的 web 请求 (阿帕奇的 HttpClient 框架

5.1 超时重试原理

        所以我们的超时重试原理,就是在 HttpClient 里边设置超时时间,然后如果超时了,还没获取到服务列表,报错信息就会一步一步返回给上层,最终给到 OpenFeign。

想要在了解的细致一些,就可以去看看 OpenFeign 的底层源码:

public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = this.buildTemplateFromArgs.create(argv);
    Request.Options options = this.findOptions(argv);
    Retryer retryer = this.retryer.clone();
    // 死循环,如果成功或者重试结束就返回 [通过throw终止while循环]
    while(true) {
        try {
            // 通过 Http Client 发起通信
            return this.executeAndDecode(template, options);
        } catch (RetryableException var9) {
            RetryableException e = var9;

            try {
                // 判断是否重试
                retryer.continueOrPropagate(e);
            } catch (RetryableException var8) {
                Throwable cause = var8.getCause();
                if (this.propagationPolicy == ExceptionPropagationPolicy.UNWRAP && cause != null) {
                    throw cause;
                }

                throw var8;
            }

            if (this.logLevel != Logger.Level.NONE) {
                this.logger.logRetry(this.metadata.configKey(), this.logLevel);
            }
        }
    }
}

源码中使用 RequestTemplate 发送 HTTP 请求,我们主要关注两个地方:

  1. this.executeAndDecode(template, options); // HttpClient 进行通信
  2. retryer.continueOrPropagate(e);  // 重试机制(默认 / 自定义)
所以,OpenFeign 的重试机制是通过其内置的 Retryer 组件和底层的 HTTP 客户端实现的。
Retryer 组件提供了重试策略的逻辑实现,而远程接口则通过 HTTP 客户端来完成调用!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1173173.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

04-附注 三维空间中的线性变换

附注 三维空间中的线性变换 三维空间线性变换 这是关于3Blue1Brown "线性代数的本质"的学习笔记。 三维空间线性变换 图1 绕y轴旋转90 绕y轴旋转90后,各基向量所在的坐标如图1所示。用旋转后的各基向量作为矩阵的列,就得到变换矩阵。变换矩阵…

简单代理模式

代理模式 代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。 结构图如下: ISubject接口,定义了RealSubject和Proxy的共用接口方法,这样就可以在任何使用RealSubject的地方使用Proxy代理。 ISubject接口 public…

心脏骤停急救赋能

文章目录 0. 背景知识1. 遇到有人突然倒地怎么办1.1 应急反应系统1.2 高质量CPR1.2.1 胸外按压1.2.2 人工呼吸 1.3 AED除颤1.3.1 AED用法 1.4 高级心肺复苏1.5 入院治疗1.6 康复 0. 背景知识 中国每30s就有人倒地,他们可能是工作压力大的年轻人(工程师群…

用Java(C语言也可以看)实现冒泡排序和折半查找(详细过程图)+逆序数组

目录 一、冒泡排序 1.冒泡排序介绍 2.排序的思路 3.完整代码 二、折半查找 1.折半查找介绍 2.查找的思路 3.完整代码 三、逆序数组 1.逆序思路 2..完整代码 一、冒泡排序 冒泡排序是众多排序的一种,无论在C语言或者Java中都很常见,后续在数据…

CleanMyMac X2024试用版下载及使用教程

CleanMyMac X是一款颇受欢迎的专业清理软件,拥有十多项强大的功能,可以进行系统清理、清空废纸篓、清除大旧型文件、程序卸载、除恶意软件、系统维护等等,并且这款清理软件操作简易,非常好上手,特别适用于那些刚入手苹…

Sync Folders Pro(文件夹数据同步工具)

Sync Folders Pro for Mac 是一款功能强大的文件夹同步工具,旨在帮助用户在 Mac 计算机和移动设备之间创建双向同步。这款软件支持各种文件系统和设备,如 iPhone,iPad,iPod,Android 等。通过这款软件,用户可…

【Linux】 基础命令 第一篇

目录 ls​编辑 ls -l ls -a ls -i ls ./* cd指令&&pwd cd . && cd .. 绝对路径: dir/Linux/2023/10 相对路径: 跳转至另一路径​编辑 cd~ cd - touch指令(创建文件) stat指令: mkdir 指令(创建文件夹) tree指…

win10设置windows永不更新

以下方法能全部设置都要全部设置。 方法一:Windows设置 要想关闭Win10自动更新,比较简单的一种方法就是进入到Windows设置中,将Windows更新直接关闭。步骤如下: 1、按“Windows I”键,打开Windows设置,再…

【C语言基础入门】二级指针、一维数组与指针、二维数组与指针

文章目录 前言一. 二级指针1.1 二级指针是什么?1.2 二级指针使用示例1.3 拓展:n级指针 二. 一维数组与指针:2.1 利用指针遍历数组:2.2 指针数组2.3 数组指针 三、 二维数组指针3.1 二维数组指针是什么?示例代码 3.2 指针数组示例代码 3.3 利用…

【Hadoop】Apache Hadoop YARN

🦄 个人主页——🎐开着拖拉机回家_Linux,Java基础学习,大数据运维-CSDN博客 🎐✨🍁 感谢点赞和关注 ,每天进步一点点!加油! 目录 一、YARN概述 二、YARN基础架构 2.1 ResourceManager&#x…

Java 正则表达式分组匹配

前几篇文章都是简单判断是否满足匹配规则,当需要提取匹配结果时就用到分组匹配。 分组匹配 可以判断是否满足正则表达式,然后提取出子串。 有些时候电话号码是以 123-4567-8899 这样显示的,我们要判断某个字符串是这种形式的并分别提起三段…

总线类设备驱动——SPI

目录 一、 SPI协议简介 二、 Linux SPI驱动 三、 SPI设备驱动范例 一、 SPI协议简介 SPI(Serial Peripheral Interface)由 Motorola 开发,他并不是严格意义上的标准协议但是几乎所有的厂商都遵从这一协议,所以可以说它是一个“事实上的”协议。SPI 是同…

学习GTEx数据库

每个个体的不同的器官组织的基因(Gene)都是相同的,但为什么有的表型为肝脏组织,帮助人类代谢?有的是肌肉组织,帮助人类运动?其原因是,不同的人体组织表达的基因并不相同。 &#xff…

行业安卓主板-基于RK3568/3288/3588的AI智能网络广告机/自动售货机/收银机解决方案(三)

广告机 智能网络广告机通过网络将音视频、图片、文档、网页等自由排版创建成节目发布到终端。可针对不同的终端统一管理,统一发布;针对应用场景的集中和分散,可以选用局域网管理和云服务器管理。 自动售货机 随着物联网、大数据、人工智能的…

2024长三角大数据产业博览会(世亚智博会)数据赋能·智创未来

2024长三角国际大数据产业博览会,定于3月份在沪召开,展会始终秉承“全球视野、国家高度、产业角度、企业立场”的办会理念,实现每届展会成功、圆满、精彩举办,集聚效应和影响力不断增强。本次展会将汇聚全球大数据产业的领军企业和…

无法加载文件 C:\Program Files\nodejs\cnpm.ps1,因为在此系统上禁止运行脚本。有

cnpm : 无法加载文件 C:\Program Files\nodejs\cnpm.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID135170 中的 about_Execution_Poli cies。 所在位置 行:1 字符: 1 cnpm run debug ~~~~ Categ…

CleanMyMac X2024永久激活码许可证

如果你拥有苹果电脑,那么就非常有必要在Mac上安装CleanMyMac,不是只有Windows才会产生垃圾,Mac同样的也会,定期清理Mac垃圾,可以释放磁盘空间。 CleanMyMac X 是一款流行的 Mac 清理工具,具有许多功能&…

paragon2024好用的NTFS工具

Mac OS X 对NTFS——Windows PC的主要文件系统——提供极少支持。您无法在NTFS分区中创建、删除或者修改文件或文件夹,而仅仅只能读取。Paragon NTFS for Mac官方版一直是Mac OS平台上最受欢迎的NTFS硬盘格式读取工具,有了NTFS for Mac ,安装…

Leetcode41缺失的第一个正数

思路:原地哈希表 长度为N的数组,没有出现过的正整数一定是1~N1中的一个。 此时会思考能不能用一个哈希表来保存出现过的1~N1的数,然后从 1 开始依次枚举正整数,并判断其是否在哈希表中 但是题目要求常数级别的空间,就不…

多级缓存之JVM进程缓存

1.什么是多级缓存 传统的缓存策略一般是请求到达Tomcat后,先查询Redis,如果未命中则查询数据库,如图: 存在下面的问题: 请求要经过Tomcat处理,Tomcat的性能成为整个系统的瓶颈 Redis缓存失效时&#xff0…