乐趣区

关于feign:open-feign-调用超时与重试

1. 前言

在 spring cloud 各种组件中,我最早接触的就是 open feign,但素来没有讲过它。起因是因为感觉它简略,无非就是个服务调用,在代码层面上也很简略,没有啥可说的。

但为什么明天来讲呢:

  1. 服务调用看起来简略,但实则是微服务治理中很重要的一环。咱们当初微服务有上百个,如何进步微服务之间调用的稳定性,是老大难的问题。网络或高并发等起因,简直每天都有个别报错是 feign 调用的。
  2. open feign 其实是封装了负载平衡、熔断等其余组件的,把握它是有难度的。

1. feign 与 openfeign

feign 是 netflix 公司写的,是 spring cloud 组件中的一个轻量级 RESTful 的 HTTP 服务客户端,是 spring cloud 中的第一代负载平衡客户端。起初 netflix 讲 feign 开源给了 spring cloud 社区,也随之停更。

openfeig 是 spring cloud 本人研发的,在 feign
的根底上反对了 spring MVC 的注解,如 @RequesMapping 等等。是 spring cloud 中的第二代负载平衡客户端。

尽管 feign 停更了,之前我也介绍过 dobbo 这类代替产品。但在服务调用这个畛域,open feign 还是有它的一席之地。

2. spring cloud 版本更迭

先讲讲 spring cloud 的版本迭代吧。在 2020 年之前,spring cloud 等版本号是依照伦敦地铁站号命名的(ABCDEFGH):

  1. Angle
  2. Brixton
  3. Camden
  4. Dalston
  5. Edgware
  6. Finchley
  7. GreenWich
  8. Hoxton

但从 2020 年开始,版本号开始以年份命名,如:2020.0.1。

spring cloud 与 spring boot 版本的对应关系如下:

spring cloud 版本 spring boot 版本
2022.x 3.0
2021.x 2.6.x、2.7.x(2021.0.3+)
2020.x 2.4.x、2.5.x(2020.0.3+)
Hoxton 2.2.x、2.3.x(SR5+)
GreenWich 2.1.x
Finchley 2.0.x
Edgware 1.5.x
Dalston 1.5.x

3. open feign 版本更迭

在 2020.x 版本之前,open feign 默认依赖了 hystrix、ribbon。

但从 2020.x 版本开始,open feign 就不再依赖 hystrix、ribbon 了。

  • 熔断:能够本人抉择熔断组件,不过须要额定引入依赖,如:resilience4j、sentinel。
  • 负载平衡:该用 spring cloud loadbalancer 代替 ribbon。

2. 示例代码

本着实际出真知的准则,咱们还是创立个我的项目试验一下。这一章节就是把示例代码的外围代码列出来。代码还是以 openfeign 的低版本为主,spring cloud 版本为 Hoxton。

示例代码是个多模块的我的项目,为了构建一个简略的 feign 服务调用的场景,构建上面 3 个子模块:

  • eureka-server:为 feign 服务调用提供注册核心。
  • demo1-app:http 服务,对外提供接口,供 demo2-app 调用。
  • demo2-app:http 服务,对外提供接口,该接口调用 demo1-app 的接口。

2.1. parent

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>pers.kerry</groupId>
    <artifactId>feign-service</artifactId>
    <version>${revision}</version>
    <name>feign-service</name>
    <description>feign-service</description>
    <packaging>pom</packaging>

    <properties>
        <revision>0.0.1-SNAPSHOT</revision>
        <java.version>8</java.version>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <spring-boot-starter.version>2.3.2.RELEASE</spring-boot-starter.version>
        <flatten-maven-plugin.version>1.2.7</flatten-maven-plugin.version>
        <lombok.version>1.18.24</lombok.version>
        <resilience4j.version>0.13.2</resilience4j.version>
    </properties>

    <modules>
        <module>eureka-server</module>
        <module>demo1-app</module>
        <module>demo2-app</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>${spring-boot-starter.version}</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.codehaus.mojo</groupId>
                    <artifactId>flatten-maven-plugin</artifactId>
                    <version>${flatten-maven-plugin.version}</version>
                    <configuration>
                        <updatePomFile>true</updatePomFile>
                        <flattenMode>clean</flattenMode>
                    </configuration>
                    <executions>
                        <execution>
                            <id>flatten</id>
                            <phase>process-resources</phase>
                            <goals>
                                <goal>flatten</goal>
                            </goals>
                        </execution>
                        <execution>
                            <id>flatten-clean</id>
                            <phase>clean</phase>
                            <goals>
                                <goal>clean</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

2.2. eureka-server

1. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>pers.kerry</groupId>
        <artifactId>feign-service</artifactId>
        <version>${revision}</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <groupId>pers.kerry</groupId>
    <artifactId>eureka-server</artifactId>
    <name>eureka-server</name>
    <description>eureka-server</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. EurekaServerApplication

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class, args);
    }
}

3. application.yml

server:
  port: 8000
spring:
  application:
    name: eureka-server
eureka:
  instance:
    hostname: localhost
  client:
    register-with-eureka: false
    fetch-registry: false

2.3. demo1-app

1. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>pers.kerry</groupId>
        <artifactId>feign-service</artifactId>
        <version>${revision}</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <groupId>pers.kerry</groupId>
    <artifactId>demo1-app</artifactId>
    <name>demo1-app</name>
    <description>demo1-app</description>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. Demo1AppApplication

@SpringBootApplication
@EnableDiscoveryClient
public class Demo1AppApplication {public static void main(String[] args) {SpringApplication.run(Demo1AppApplication.class, args);
    }

}

3. DemoController

@RestController
@RequestMapping
@Slf4j
public class DemoController {@GetMapping("hello")
    public String hello(@RequestParam Integer seconds) {if (seconds < 0) {throw new RuntimeException("工夫不能为正数");
        }
        try {Thread.sleep(seconds * 1000);
        } catch (InterruptedException e) {throw new RuntimeException(e);
        }
        log.info("app1: 你好!");
        return "hello";
    }
}

4. application.yml

server:
  port: 8001
spring:
  application:
    name: app1
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

2.4. demo2-app

1. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>pers.kerry</groupId>
        <artifactId>feign-service</artifactId>
        <version>${revision}</version>
        <relativePath>../pom.xml</relativePath>
    </parent>

    <groupId>pers.kerry</groupId>
    <artifactId>demo2-app</artifactId>
    <name>demo2-app</name>
    <description>demo2-app</description>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. Demo2AppApplication

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class Demo2AppApplication {public static void main(String[] args) {SpringApplication.run(Demo2AppApplication.class, args);
    }

}

3. DemoController

@RestController
@RequestMapping("feign")
@AllArgsConstructor
@Slf4j
public class DemoController {
    private final HelloFeign helloFeign;

    @GetMapping("hello")
    public String hello(@RequestParam Integer seconds) {log.info("app2: 你好!");
        return helloFeign.hello(seconds);
    }
}

4. HelloFeign

@FeignClient(name = "app1")
public interface HelloFeign {@GetMapping("hello")
    String hello(@RequestParam Integer seconds);

}

5. application.yml

server:
  port: 8002
spring:
  application:
    name: app2

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka

3. feign 超时、重试

3.1. feign 超时

1. 设置

当咱们在 demo2-app 的 application.yml 文件中仅增加 feign 的配置:

server:
  port: 8002
spring:
  application:
    name: app2

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka
feign:
  client:
    config:
      default:
        connect-timeout: 1000
        read-timeout: 2500

spring 配置类中注册 bean:

    @Bean
    public Retryer retryer(){return new Retryer.Default(100,1000,3);
    }

feign 的重试是通过 retryer 属性实现的,但如果须要自定义重试策略,则须要写代码注册 bean。

依照 Retryer 类构造方法中参数程序顺次为:

  • period: 初始重试距离,默认实现值是 100 ms
  • maxPeriod: 最大重试距离,默认实现值是 1000 ms
  • maxAttempts: 最大重试次数,初始调用算一次,默认实现值是 5

2. 测试用例 1

调用接口:(GET) http://localhost:8002/feign/h…

执行后果依照工夫程序是:

  1. app1 打印 1 次数(“app1: 你好!”)
  2. app2 胜利返回“hello”

3. 测试用例 2

调用接口:(GET) http://localhost:8002/feign/h…

执行后果依照工夫程序是:

  1. app1 打印 3 次数(“app1: 你好!”)后
  2. app2 接口报错。谬误:feign.RetryableException

4. 剖析

在配置中,咱们设置申请解决工夫(readTimeout)为 2.5 秒,失败后重试 2 次(减去初始调用的 1 次)。

在测试用例 1 中,因为设置 app1 解决工夫在 2 秒,没有超过 2.5 秒,所以失常申请胜利,app1 只打印了 1 次。

在测试用例 2 中,因为设置 app1 解决工夫在 3 秒,超过了 2.5 秒,单次申请失败,触发了失败重试机制。首次执行了 1 次,又重试了 2 次,所以一共有 3 次调用,app1 共打印了 3 次。

5. feign 配置

  • connect-timeout: 申请连贯的超时工夫(毫秒)
  • read-timeout: 申请解决的超时工夫(毫秒)
  • retryer: 重试的实现类(如:feign.Retryer.Default)。如果不配置,则默认不重试

6. 部分配置

其实失常的配置前缀应该叫 feign.config.client.${feignName}。能够针对不同的 feign 调用服务(@FeignClient 中的 name 属性值),配置不同的策略。上述的 feign.config.client.default 是设置默认配置。

如下列可针对 app1、appx 配置不同策略:

feign:
  client:
    config:
      app1:
        connect-timeout: 1000
        read-timeout: 2500
        retryer: feign.Retryer.Default
      appx:
        connect-timeout: 1000
        read-timeout: 4500
        retryer: pers.kerry.demo2app.config.AppXRetryer

3.2. feign 重试(retryer)

1. 全局配置

上述中在 配置类(@Configuration) 中注册 Retryer Bean,就是全局配置,所有服务都走这同一个策略。如下:

@SpringBootConfiguration
public class AppFeignConfig {
    @Bean
    public Retryer retryer(){return new Retryer.Default(100,1000,3);
    }
}

要留神的是,一旦在注册了 bean,就算 feign.config.client.${feignName}.retryer 为空,也不会敞开重试策略,仍然失效。所以这种形式要谨慎!

2. 部分配置(指定 Bean 配置类)

和下面的例子很像,同样在类中申明 Retryer Bean,但并非在配置类中,只是作为 feign client 指定的逻辑上“配置类”。如下:

public class AppFeignConfig {
    @Bean
    public Retryer retryer(){return new Retryer.Default(100,1000,3);
    }
}

而后在 HelloFeign.java 中指定配置类:

@FeignClient(name = "app1",configuration = AppFeignConfig.class)
public interface HelloFeign {@GetMapping("hello")
    String hello(@RequestParam Integer seconds);

}

此时 feign.config.client.${feignName}.retryer 能够为空,因为读的是 @FeignClient 的配置了。

3. 部分配置(指定类门路)

可自定义类继承 Retryer 默认类(feign.Retryer.Default),可通过设置默认构造方法,来定义重试规定,如下:

pers.kerry.demo2app.config.AppXRetryer.java

public class AppXRetryer extends Retryer.Default {
    private static final int maxAttempts = 2;
    private static final long period = 100;
    private static final long maxPeriod = 1500;

    public AppXRetryer() {super(period, maxPeriod, maxAttempts);
    }

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

其在 application.yml 上配置的形式是:

feign:
  client:
    config:
      default:
        connect-timeout: 1000
        read-timeout: 1500
        retryer: pers.kerry.demo2app.config.AppXRetryer

4. ribbon 超时、重试

1. 设置

当咱们在 demo2-app 的 application.yml 文件中仅增加 ribbon 的配置:

server:
  port: 8002
spring:
  application:
    name: app2
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8000/eureka
feign:
  hystrix:
    enabled: false
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 2500
  MaxAutoRetries: 3
  MaxAutoRetriesNextServer: 0

2. 测试用例 1

调用接口:(GET) http://localhost:8002/feign/h…

执行后果依照工夫程序是:

  1. app1 打印 1 次数(“app1: 你好!”)
  2. app2 胜利返回“hello”

3. 测试用例 2

调用接口:(GET) http://localhost:8002/feign/h…

执行后果依照工夫程序是:

  1. app1 打印 4 次数(“app1: 你好!”)后
  2. app2 接口报错。谬误:feign.RetryableException

4. 剖析

在配置中,咱们设置申请解决工夫(ReadTimeout)为 2.5 秒,失败后重试 3 次。

在测试用例 1 中,因为设置 app1 解决工夫在 2 秒,没有超过 2.5 秒,所以失常申请胜利,app1 只打印了 1 次。

在测试用例 2 中,因为设置 app1 解决工夫在 3 秒,超过了 2.5 秒,单次申请失败,触发了失败重试机制。因为首次执行了 1 次,又重试了 3 次,所以一共有 4 次调用,app1 共打印了 4 次。

5. ribbon 配置

  • ConnectTimeout: 申请连贯的超时工夫(毫秒)
  • ReadTimeout: 申请解决的超时工夫(毫秒)
  • MaxAutoRetries: 同一实例最大重试次数,不包含首次调用。默认值为 0
  • MaxAutoRetriesNextServer: 同一个服务其余实例的最大重试次数,不包含第一次调用的实例。默认值为 1
  • OkToRetryOnAllOperations: 是否所有操作都容许重试。默认值为 false,即只在 GET 协定上重试所有谬误
  • ServerListRefreshInterval: Ribbon 更新服务注册列表的频率(毫秒)

6. 部分配置

可针对不必的 feign 调用服务(@FeignClient 中的 name 属性值),配置不同的策略。如,下列可针对 app1、appx 配置不同策略:

app1:
  ribbon:
    ConnectTimeout: 1000
    ReadTimeout: 2500
    MaxAutoRetries: 3
    MaxAutoRetriesNextServer: 0
appn:
  ribbon:
    ConnectTimeout: 1000
    ReadTimeout: 4500
    MaxAutoRetries: 0
    MaxAutoRetriesNextServer: 3

5. feign、ribbon 比拟

1. 比拟

feign 的配置策略更丰盛,至多 idea 会有提醒。

但在失败重试的方向上,ribbon 性能更弱小。不仅是配置起来更简略,而且反对跨服务重试,这个在理论利用中很重要。毕竟当某个服务因高并发而短暂阻塞时,最好的解决办法就是引流到其余服务上重试。

2. 优先级

当上述 application 文件中,feign、ribbon 同时开启配置如下:

feign:
  hystrix:
    enabled: false
  client:
    config:
      default:
        connect-timeout: 1000
        read-timeout: 1500
        retryer: pers.kerry.demo2app.config.AppXRetryer
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 2500
  MaxAutoRetries: 3
  MaxAutoRetriesNextServer: 0

在测试时发现无论超时还是重试,以后失效的只有 feign 的配置。

可见默认状况下,feign 配置的优先级要高于 ribbon。

因为有一个 feign.client.default-to-properties 的属性,其作用是初始化对象获取属性的优先级程序。因为默认值为 true,即 feign 配置的优先级最高。如果手动设置为 false,则能够以 ribbon 的配置失效。

6. 熔断 hystrix(feign 低版本)

hystrix 是由 netflix 开源的一款容错框架,蕴含隔离(线程池隔离、信号量隔离)、熔断、降级回退和缓存容错、缓存、批量解决申请、主从分担等罕用性能。

feign 自身反对 hystrix,默认是敞开 hystrix 的,须要在配置文件中开启 feign.hystrix.enabled=true,默认值为 false。

6.1. hystrix 测试

因为 feign 默认就引入了 hystrix,在开启 feign.hystrix 后,只须要设置 hystrix 的配置就能够了。如上面的配置,再测试一下:

feign:
  client:
    config:
      default:
        connect-timeout: 1000
        read-timeout: 1500
        retryer: pers.kerry.demo2app.config.AppXRetryer
    default-to-properties: true
  hystrix:
    enabled: true

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          strategy: THREAD
          thread:
            timeoutInMilliseconds: 2500

1. 测试用例 1

调用接口:(GET) http://localhost:8002/feign/h…

执行后果依照工夫程序是:

  1. app1 打印 1 次数(“app1: 你好!”)
  2. app2 胜利返回“hello”

2. 测试用例 2

调用接口:(GET) http://localhost:8002/feign/h…

执行后果依照工夫程序是:

  1. app1 打印 2 次数(“app1: 你好!”)后
  2. app2 接口报错。谬误:com.netflix.hystrix.exception.HystrixRuntimeException

3. 测试用例 3

调用接口:(GET) http://localhost:8002/feign/h…

执行后果依照工夫程序是:

  1. app1 打印 1 次数(“app1: 你好!”)
  2. app2 接口报错。谬误:com.netflix.hystrix.exception.HystrixRuntimeException
  3. app1 再打印 1 次数(“app1: 你好!”)

如果不思考 hystrix 的因素,当申请 seconds 值为 3 时,应该是和值为 2 时一样,在重试 1 次后再中断请求报错。

但因为 3 大于 hystrix 设置的超时工夫 2.5,在第一次申请时就触发了熔断报错。不过因为 feign 的重试机制,仍然再重试了 1 次,但属于有效的重试,毕竟 app2 接口的 http 申请曾经终结了。

所以如果须要开启 hystrix 熔断,各自超时工夫的值,须要好好搭配一下。

6.2. hystrix 配置

当开启 feign.hystrix 后,可参考下列默认配置。

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true # 开启超时熔断
        isolation:
          strategy: THREAD
          semaphore:
            maxConcurrentRequests: 100 # 默认最大 100 个信号量并发,业务可依据具体情况调整(strategy=semaphore 时失效)thread:
            timeoutInMilliseconds: 10000 # 默认熔断工夫 10 秒,须要大于 ribbon 的 retry*timeout
      #熔断策略
      circuitBreaker:
        enabled: true # 启用熔断
        requestVolumeThreshold: 20 # 度量窗口内申请量阈值,熔断前置条件,默认 20
        errorThresholdPercentage: 50 # 谬误阈值比例,超过则触发熔断,默认 50%
        sleepWindowInMilliseconds: 5000 # 等待时间后从新查看申请,默认 5 秒
  threadpool:
    default:
      coreSize: 10 # 外围数量,默认 10,可依据理论业务调整
      maximumSize: 10 # 最大数量,默认 10,可依据理论业务调整
      allowMaximumSizeToDivergeFromCoreSize: true # 是否容许从 coreSize 裁减到 maximumSize
      maxQueueSize: 1000 # 队列最大数量,不反对动静配置
      queueSizeRejectionThreshold: 500 # 队列数量阈值,可动静配置
      keepAliveTimeMinutes: 2

理论的配置项可看 hystrix 官网文档。本文要特别强调的是:

hystrix 超时时长 > (ribbon 超时时长 ribbon 重试次数 ) or (feign 超时时长 feign 重试次数 )

退出移动版