SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign)
作者:mmseoamin日期:2023-12-11

目录

    • 一、简介
    • 二、feign和OpenFeign的区别
    • 三、SpringCloud集成OpenFeign
      • 1、引入starter
      • 2、在启动类或者配置类上加@EnableFeignClients注解
      • 3、声明Feign接口
      • 4、@FeignClient 属性介绍
      • 5、@EnableFeignClients 属性介绍
      • 四、默认配置
        • 1、替换默认配置前置说明(这里以Feign请求日志为例)
        • 2、使用配置文件替换默认配置(推荐使用优先级最高)
          • 2.1、全局请求日志配置
          • 2.2、独立请求日志配置(独立配置优先)
          • 2.3、源码分析为什么使用配置文件配置优先级最高
          • 3、在@EnableFeignClients中使用缺省(默认)配置类替换默认配置(如果不做特殊处理这个配置类优先级很高会比独立配置还高)
            • 3.1、配置实现
            • 3.2、源码分析为什么使用缺省配置类优先级会那么高
            • 4、使用独立配置替换默认配置
            • 五、解决allow-bean-definition-overriding问题
              • 1、错误原因
              • 2、解决方法一(别用)
              • 3、解决方法二(推荐)
              • 六、解决GET请求无法传递对象参数问题(使用@SpringQueryMap)
              • 七、OpenFeign超时配置
                • 1、使用配置文件配置(推荐)
                • 2、使用@FeignClient配置超时时间
                • 3、为单独接口设置超时时间(这种方式优先级比使用配置文件更高)
                • 4、通过Ribbon配置文件设置超时时间(不主动设置Feign的超时时间才有效,不推荐使用)
                • 八、OpenFeign重试配置
                  • 1、OpenFeign默认重试机制(核心)
                  • 2、通过Ribbon配置重试(配置了Feign的配置或者spring-retry则Ribbon重试配置会失效)
                  • 3、通过Feign的Retryer接口配置重试(如果在配置文件设置了Feign的Retryer那么Ribbon和spring-retry的重试机制都会失效)
                  • 4、通过spring-retry为每个请求设置重试(如果Feign配置自己的Retryer那么spring-retry不会生效,但是会比Ribbon优先级高)
                  • 九、OpenFeign请求日志级别配置
                    • 1、通过配置文件配置(推荐)
                    • 2、使用@FeignClient配置
                    • 十、OpenFeign拦截器
                      • 1、通过OpenFeign请求拦截器设置统一请求头
                      • 十一、替换OpenFeign默认HTTP请求框架HttpURLConnection
                        • 1、替换成HttpClient
                        • 2、替换成OkHttp
                        • 3、源码断点查看是否生效
                        • 十二、OpenFeign配置hystrix
                          • 1、使用@FeignClient中的fallback进行回调(不能控制不同异常的处理逻辑)
                          • 2、使用@FeignClient中的fallbackFactory进行回调(可以控制对应异常的处理逻辑)
                          • 十三、配置Ribbon负载均衡算法
                            • 1、单独配置某个服务负载均衡算法(使用全局配置无效)
                            • 2、为什么使用配置文件配置Ribbon的全局负载均衡算法会无效?

                              一、简介

                              OpenFeign客户端是一个web声明式http远程调用工具,直接可以根据服务名称去注册中心拿到指定的服务IP集合,提供了接口和注解方式进行调用,内嵌集成了Ribbon本地负载均衡器。

                              二、feign和OpenFeign的区别

                              1、底层都是内置了Ribbon,去调用注册中心的服务。

                              2、Feign是Netflix公司写的,是SpringCloud组件中的一个轻量级RESTful的HTTP服务客户端,是SpringCloud中的第一代负载均衡客户端。

                              3、OpenFeign是SpringCloud自己研发的,在Feign的基础上支持了Spring MVC的注解,如@RequesMapping等等。是SpringCloud中的第二代负载均衡客户端。

                              4、Feign本身不支持Spring MVC的注解,使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务

                              5、OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

                              三、SpringCloud集成OpenFeign

                              版本说明:

                              Spring Cloud Version:Hoxton.SR12

                              Spring Boot Version:2.3.12.RELEASE

                              不同版本源码可能会有差异

                              1、引入starter

                              
                              
                                  org.springframework.cloud
                                  spring-cloud-starter-openfeign
                              
                              

                              2、在启动类或者配置类上加@EnableFeignClients注解

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

                              3、声明Feign接口

                              @FeignClient(value = "kerwin-user",contextId = "userInfoClient")
                              public interface UserInfoClient {
                                  /** 获取用户信息 */
                                  @GetMapping("/user-info/info/{id}")
                                  String getInfo(@PathVariable("id") Long id);
                              }
                              

                              4、@FeignClient 属性介绍

                              @FeignClient用于标记一个接口为Feign客户端,@FeignClient中的属性可以使用 ${feign.name} 这种方式取值

                              @Target(ElementType.TYPE)
                              @Retention(RetentionPolicy.RUNTIME)
                              @Documented
                              @Inherited
                              public @interface FeignClient {
                              	// name和value属性用于标注客户端名称,也可以用${propertyKey}获取配置属性
                              	@AliasFor("name")
                              	String value() default "";
                              	// 该类的Bean名称 PS: 这个配置很重要后续会详细介绍
                              	String contextId() default "";
                              	// name和value属性用于标注客户端名称,也可以用${propertyKey}获取配置属性
                              	@AliasFor("value")
                              	String name() default "";
                              	// 弃用 被qualifiers()替代。
                              	@Deprecated
                              	String qualifier() default "";
                              	// 模拟客户端的@Qualifiers值。如果qualifier()和qualifiers()都存在,我们将使用后者,除非qualifier()返回的数组为空或只包含空值或空白值,在这种情况下,我们将首先退回到qualifier(),如果也不存在,则使用default = contextId + "FeignClient"。
                              	String[] qualifiers() default {};
                              	// 绝对URL或可解析主机名 PS: 使用这个配置后只能定型发送,没有负载均衡能力
                              	String url() default "";
                              	// 是否应该解码404而不是抛出FeignExceptions
                              	boolean decode404() default false;
                              	// 用于模拟客户端的自定义配置类。可以包含组成客户端部分的覆盖@Bean定义,默认配置都在FeignClientsConfiguration类中,可以指定FeignClientsConfiguration类中所有的配置
                              	Class[] configuration() default {};
                              	// 指定失败回调类
                              	Class fallback() default void.class;
                              	// 为指定的假客户端接口定义一个fallback工厂。fallback工厂必须生成fallback类的实例,这些实例实现了由FeignClient注释的接口。
                              	Class fallbackFactory() default void.class;
                              	// 所有方法级映射使用的路径前缀
                              	String path() default "";
                              	// 是否将虚拟代理标记为主bean。默认为true。
                              	boolean primary() default true;
                              }
                              

                              5、@EnableFeignClients 属性介绍

                              @Retention(RetentionPolicy.RUNTIME)
                              @Target(ElementType.TYPE)
                              @Documented
                              @Import(FeignClientsRegistrar.class)
                              public @interface EnableFeignClients {
                              	// 扫描@FeignClient包地址
                              	String[] value() default {};
                              	// 用户扫描Feign客户端的包,也就是@FeignClient标注的类,与value同义,并且互斥
                              	String[] basePackages() default {};
                              	// basePackages()的类型安全替代方案,用于指定要扫描带注释的组件的包。每个指定类别的包将被扫描。 考虑在每个包中创建一个特殊的无操作标记类或接口,除了被该属性引用之外没有其他用途。
                              	Class[] basePackageClasses() default {};
                              	// 为所有假客户端定制@Configuration,默认配置都在FeignClientsConfiguration中,可以自己定制
                              	Class[] defaultConfiguration() default {};
                              	// 可以指定@FeignClient标注的类,如果不为空,就会禁用类路径扫描
                              	Class[] clients() default {};
                              }
                              

                              四、默认配置

                              • 我们使用的很多框架都会有自己的默认配置,尤其是和SpringBoot集成的starter包,OpenFeign集成SpringBoot的starter包同样的也会给我们做很多默认配置。

                              • FeignClientsConfiguration 类中,OpenFeign为我们做了很多默认配置,这个默认配置我们都可以自定义并且覆盖。

                                1、替换默认配置前置说明(这里以Feign请求日志为例)

                                • 自定义并且覆盖默认配置有两个维度,一个是使用全局配置另一个是每个FeignClient使用独立配置,这里以OpenFeign请求日志配置做介绍。

                                • Feign远程调用时提供了日志打印功能,输出的日志级别为debug,先要把项目的输出日志设置为debug。

                                  // 设置指定日志输出级别为debug,我这里将我自己com.kerwin包所有日志输出级别设置成了debug,根据自己需要来即可
                                  logging:
                                    level:
                                      com:
                                        kerwin: debug
                                  
                                • 为了更加精细化控制日志输出,Feign还提供了日志内容输出的几个级别。

                                  NONE:默认的,不显示任何日志。

                                  BASIC:仅记录请求方法和URL以及响应状态代码和执行时间。

                                  HEADERS:除了BASIC中定义的信息之外,还有请求和响应头的信息。

                                  FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

                                  2、使用配置文件替换默认配置(推荐使用优先级最高)

                                  2.1、全局请求日志配置
                                  feign:
                                    client:
                                      config:
                                        # 默认配置 如果不单独配置每个服务会走默认配置
                                        default:
                                          loggerLevel: FULL # 日志级别 NONE:不打印  BASIC:打印简单信息 HEADERS:打印头信息 FULL:打印全部信息 (默认 NONE)
                                  
                                  2.2、独立请求日志配置(独立配置优先)
                                  feign:
                                    client:
                                      config:
                                        # 配置单独FeignClient
                                        # @FeignClient(value = "kerwin-user",contextId = "userInfoClient")
                                        # 如果FeignClient注解设置了contextId这里就使用userInfoClient如果没有设置contextId就直接使用服务名称kerwin-user
                                        userInfoClient:
                                          loggerLevel: FULL # 日志级别 NONE:默认不打印  BASIC:打印简单信息 HEADERS:打印头信息 FULL:打印全部信息(默认 NONE)
                                  
                                  2.3、源码分析为什么使用配置文件配置优先级最高

                                  通过配置文件配置其实是最后才加载的,会将其它地方配置的信息全部顶掉有兴趣可以看看源码 FeignClientFactoryBean.configureFeign 这个方法会比其它配置加载后执行,会在这里实现替换, 替换顺序是Spring容器中的配置Bean -> 配置文件中的默认配置 -> 配置文件中的独立配置

                                  SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign),在这里插入图片描述,第1张

                                  3、在@EnableFeignClients中使用缺省(默认)配置类替换默认配置(如果不做特殊处理这个配置类优先级很高会比独立配置还高)

                                  3.1、配置实现
                                  • 先写一个类提供一个日志方法feignLoggerLevel设置一个日志等级,并且使用@Bean和@ConditionalOnMissingBean 标记。

                                    PS:需要特别注意这个类上不能加@Configuration这类注解,如果被Spring扫描到了那么全局都会使用这一个配置

                                    public class FeignCommonSpecification {
                                        @Bean
                                        @ConditionalOnMissingBean //这里如果不加@ConditionalOnMissingBean那么独立配置是无法生效的,原理在后面补充
                                        public Logger.Level feignLoggerLevel() {
                                            return Logger.Level.FULL;
                                        }
                                    }
                                    
                                    • 在@EnableFeignClients中添加defaultConfiguration
                                      @EnableFeignClients(defaultConfiguration = FeignCommonSpecification.class)
                                      
                                      3.2、源码分析为什么使用缺省配置类优先级会那么高

                                      要分析这个问题首先要知道Spring的 allowBeanDefinitionOverriding 参数是什么,这里简单解释一下,这个参数是控制注册 BeanDefinition 是否可以覆盖加载的,也就是说当这个参数为true时Bean的名称相同是可以覆盖的,在Spring的 DefaultListableBeanFactory 中我们可以看到这个参数默认为true,也就是说谁后加载那么在 BeanDefinitionMap 中就注册的就是谁,在 NamedContextFactory 的 createContext 方法中我们可以看到是先注册的独立配置然后在注册的缺省配置,到这里就很明显了缺省配置把独立配置顶替了,在缺省配置的方法中加上 @ConditionalOnMissingBean 可以解决这个问题,还有一点要提一下在SpringBoot中会将 allowBeanDefinitionOverriding 设置为false,但是每个 FeignClient 都会通过创建 NamedContextFactory 的 createContext 方法创建一个自己的 AnnotationConfigApplicationContext 子容器,这个子容器中 allowBeanDefinitionOverriding 默认还是true。

                                      SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign),在这里插入图片描述,第2张

                                      4、使用独立配置替换默认配置

                                      • 先写一个类提供一个日志方法feignLoggerLevel设置一个日志等级,并且使用@Bean标记。

                                        PS:需要特别注意这个类上不能加@Configuration这类注解,如果被Spring扫描到了那么全局都会使用这一个配置

                                        public class UserInfoClientSpecification  {
                                            @Bean
                                            public Logger.Level feignLoggerLevel() {
                                                return Logger.Level.FULL;
                                            }
                                        }
                                        
                                        • 在@FeignClient中添加configuration
                                          @FeignClient(value = "kerwin-user",contextId = "userInfoClient",configuration = UserInfoClientSpecification.class)
                                          

                                          五、解决allow-bean-definition-overriding问题

                                          启动项目时出现这个错误信息

                                          Description:

                                          The bean ‘kerwin-user.FeignClientSpecification’ could not be registered. A bean with that name has already been defined and overriding is disabled.

                                          Action:

                                          Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

                                          1、错误原因

                                          这个错误是因为出现了重名的Bean名称,在FeignClientsRegistrar 中注册Feign 配置信息到Spring 中时会组装一个Bean 的名称,如果在**@FeignClient** 中没有设置contextId 那么会使用value 作为组装名称的前缀,比如我们有多个**@FeignClient** 设置的value 服务名称为user 并且没有设置contextId ,那么在注册Bean 信息的时候BeanName 就会重复,Spring 原生是支持覆盖的,也就是说如果BeanName 相同那么后注册的就会将先注册的替换,但是在SpringBoot 中会将这个覆盖配置关闭,如果出现一样的BeanName 那么就会抛出上面那个异常信息,解决方法常用有两种开启第一种SpringBoot 覆盖配置、第二种在**@FeignClient** 中设置contextId ,推荐使用第二种,第一种方法别用很坑,网上很多人都是使用第一种方法解决我们公司也是使用的第一种😀。

                                          2、解决方法一(别用)

                                          开启Spring覆盖加载配置,Spring默认是开启的,SpringBoot默认关闭了有兴趣可以看看SpringApplication中的allowBeanDefinitionOverriding属性。

                                          spring:
                                            main:
                                              allow-bean-definition-overriding: true
                                          

                                          3、解决方法二(推荐)

                                          给每个@FeignClient都设置自己唯一的contextId

                                          @FeignClient(value = "kerwin-user",contextId = "userInfoClient")
                                          public interface UserInfoClient {
                                          }
                                          

                                          六、解决GET请求无法传递对象参数问题(使用@SpringQueryMap)

                                          Spring Cloud OpenFeign提供了一个等价的@SpringQueryMap注释,用于将POJO或Map参数注释为查询参数Map,在参数前加上@SpringQueryMap即可。

                                          @FeignClient(value = "demo",contextId = "demoClient")
                                          public interface DemoClient {
                                              @GetMapping(path = "/demo1")
                                              String demo1(@SpringQueryMap DemoDTO dto);
                                          }
                                          

                                          七、OpenFeign超时配置

                                          OpenFeign超时有多种配置方式,这里介绍几种个人觉得最实用的

                                          1、使用配置文件配置(推荐)

                                          使用配置文件配置是最推荐的,也是在项目中使用最多的。

                                          feign:
                                            client:
                                              config:
                                                # 默认配置 如果不单独配置每个服务会走默认配置
                                                default:
                                                  connectTimeout: 2000 # 连接超时时间 默认值:10000毫秒
                                                  readTimeout: 5000 # 读取超时时间 默认值:60000毫秒
                                                # 配置单独FeignClient
                                                # @FeignClient(value = "kerwin-user",contextId = "userInfoClient")
                                                # 如果FeignClient注解设置了contextId这里就使用contextId=userInfoClient如果没有设置contextId就直接使用服务名称kerwin-user
                                                userInfoClient:
                                                  connectTimeout: 2000 # 连接超时时间 默认值:10000毫秒
                                                  readTimeout: 5000 # 读取超时时间 默认值:60000毫秒
                                          

                                          2、使用@FeignClient配置超时时间

                                          使用@FeignClient注解的configuration属性来指定配置类。

                                          首先,创建一个配置类,继承自feign.Request.Options类

                                          public class UserInfoClientSpecification extends Request.Options {
                                              public UserInfoClientSpecification() {
                                              	// 设置连接超时时间为2秒,设置读取超时时间为5秒
                                                  super(2, TimeUnit.SECONDS, 5, TimeUnit.SECONDS, true);
                                              }
                                          }
                                          

                                          然后,在使用@FeignClient注解进行声明时,使用configuration属性指定该配置类。

                                          @FeignClient(value = "kerwin-user",contextId = "userInfoClient",configuration = UserInfoClientSpecification.class)
                                          public interface UserInfoClient{
                                          }
                                          

                                          3、为单独接口设置超时时间(这种方式优先级比使用配置文件更高)

                                          在feign接口里加入Request.Options这个参数就可以单独为接口单独设置超时时间了

                                          @GetMapping("/user-info/info/{id}")
                                          String getInfo(Request.Options options,@PathVariable("id") Long id);
                                          

                                          调用的时候new 一下Options对象

                                          String resp = userInfoClient.getInfo(
                                                  new Request.Options(2, TimeUnit.SECONDS, 5, TimeUnit.SECONDS, true),
                                                  666L);
                                          

                                          4、通过Ribbon配置文件设置超时时间(不主动设置Feign的超时时间才有效,不推荐使用)

                                          ribbon:
                                            ConnectTimeout: 2000  #默认 1000毫秒
                                            ReadTimeout: 5000  #默认 1000毫秒
                                          

                                          通过Ribbon配置文件设置超时时间只有在不做任何Feign的超时时间才有效,在LoadBalancerFeignClient.getClientConfig()方法中有一个判断,当Feign的 Request.Options = 默认的Request.Options时会使用Ribbon的超时配置。

                                          SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign),在这里插入图片描述,第3张

                                          八、OpenFeign重试配置

                                          1、OpenFeign默认重试机制(核心)

                                          当不做任何配置时OpenFeign默认是会在 FeignLoadBalancer.getRequestSpecificRetryHandler() 方法中获取请求重试处理器,这里可以看到第一个判断就是获取Ribbon中是否有开启 OkToRetryOnAllOperations 配置,如果Ribbon配置中开启了那么连接超时和和其它异常导致出问题都会进行重试,重试的次数也会使用Ribbon中配置的重试次数参数。

                                          SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign),在这里插入图片描述,第4张

                                          如果Ribbon没有开启 OkToRetryOnAllOperations 配置就会进行下面的判断,当请求不为 GET 请求时只会对连接超时异常进行重试,对其它异常不会重试,因为增删改请求做重试的话可能会导致同一个数据被插入两次,对查询请求做重试不会影响正常业务。

                                          我这里调试使用的是 GET 请求,会进最下面一个方法,所有异常都会重试,这里重点看一下 **RequestSpecificRetryHandler ** 这个类,这个类是Ribbon处理重试机制的核心类,调试进入这个类的构造方法可以看到封装了重试所需要的一些参数,这些参数都是拿的Ribbon默认的参数在后面会具体说如何配置。

                                          SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign),在这里插入图片描述,第5张

                                          还有一点需要注意如果配置OpenFeign的其它配置,比如在配置文件中配置了Feign的default配置那么这里读取到的IClientConfig中的配置信息就不会是Ribbon的而是OpenFeign自己的配置,那么这个就没有Ribbon这两个重试次数的配置,结合上面几点其实不建议使用Ribbon的重试配置。

                                          SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign),在这里插入图片描述,第6张

                                          总结:也就是说OpenFeign不做任何配置,默认会对不同服务实例切换重试一次,如果不是GET请求那么只会对连接超时进行重试,如请求超时则不会重试,如果对Feign做了哪怕一个配置比如在配置文件中配置了Feign的default请求超时配置那么就无法使用Ribbon的两个重试次数的配置参数默认就是不会进行重试。

                                          2、通过Ribbon配置重试(配置了Feign的配置或者spring-retry则Ribbon重试配置会失效)

                                          一定需要注意一点,如果对Feign做了哪怕一个配置比如在配置文件中配置了Feign的default请求超时配置那么就无法使用Ribbon的两个重试次数的配置参数,默认就是不会进行重试

                                          ribbon:
                                            MaxAutoRetries: 1 #同一台实例最大重试次数,不包括首次调用  默认0
                                            MaxAutoRetriesNextServer: 1 #服务实例切换重试次数 默认1
                                            OkToRetryOnAllOperations: true #是否开启重试机制 默认关闭
                                          

                                          FeignLoadBalancer.getRequestSpecificRetryHandler() 方法的入参IClientConfig中可以看到properties中的两个重试次数配置还有是否开启重试机制开关

                                          SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign),在这里插入图片描述,第7张

                                          3、通过Feign的Retryer接口配置重试(如果在配置文件设置了Feign的Retryer那么Ribbon和spring-retry的重试机制都会失效)

                                          定义一个类继承Retryer.Default

                                          /**
                                           * 重试策略  默认 重试间隔100毫秒  最大间隔时间1秒  重试5次
                                           */
                                          @Slf4j
                                          public  class CommonFeignRetry extends Retryer.Default {
                                              public CommonFeignRetry() {
                                                  // 重试间隔是每次失败之后会等待100毫秒再次发起重试请求的间隔时间
                                                  // 最大间隔时间是每次重试的间隔时间累加起来不能超过这个最大间隔时间,如果超过了就不会在重试,哪怕还没有达到配置的重试次数
                                                  // 重试次数会受最大间隔时间和重试间隔时间影响,如果累计间隔时间超过这个最大间隔时间就不会在重试
                                                  // 重试间隔100毫秒  最大间隔时间1秒  重试5次
                                                  this(100, SECONDS.toMillis(1), 5);
                                              }
                                              public CommonFeignRetry(long period, long maxPeriod, int maxAttempts) {
                                                  super(period, maxPeriod, maxAttempts);
                                              }
                                              @Override
                                              public void continueOrPropagate(RetryableException e) {
                                                  log.warn("【FeignRetryAble】Message【{}】", e.getMessage());
                                                  super.continueOrPropagate(e);
                                              }
                                              @Override
                                              public Retryer clone() {
                                                  return new CommonFeignRetry();
                                              }
                                          }
                                          

                                          通过配置文件加载重试配置类

                                          可以做全局默认配置,也可以单独给某个FeignClient配置

                                          feign:
                                            client:
                                              config:
                                                # 默认配置 如果不单独配置每个服务会走默认配置
                                                default:
                                                  retryer: com.kerwin.config.CommonFeignRetry
                                                # 配置单独FeignClient
                                                # @FeignClient(value = "kerwin-user",contextId = "userInfoClient")
                                                # 如果FeignClient注解设置了contextId这里就使用contextId=userInfoClient如果没有设置contextId就直接使用服务名称kerwin-user
                                                userInfoClient:
                                                  retryer: com.kerwin.config.CommonFeignRetry
                                          

                                          默认的Retryer是在FeignClientsConfiguration 配置类的feignRetryer() 方法中加载的,如果出现异常会直接抛出这个异常不会进行重试处理,在SynchronousMethodHandlerinvoke() 方法中可以看到调用的是那个Retryer,有兴趣可以看看源码

                                          SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign),在这里插入图片描述,第8张

                                          PS:除了可以通过配置文件配置,当然也可以通过配置类配置,方法和日志配置差不多。

                                          4、通过spring-retry为每个请求设置重试(如果Feign配置自己的Retryer那么spring-retry不会生效,但是会比Ribbon优先级高)

                                          首先要引入spring-retry包,可以不用写版本会使用SpringBoot夫包指定的版本

                                          
                                              org.springframework.retry
                                              spring-retry
                                          
                                          

                                          如果我们需要为特定的请求设置不同的重试策略,则可以在对应的方法上加上 @Retryable 注解,并指定对应的 Retryer 类型,如下所示:

                                          @FeignClient(value = "kerwin-user",contextId = "userInfoClient")
                                          public interface UserInfoClient{
                                              @GetMapping("/user-info/get-user-name/{id}")
                                              @Retryable(maxAttempts = 2)
                                              String getUserName(@PathVariable("id") Long id);
                                          }
                                          

                                          使用上述方式,我们可以为每个请求设置不同的重试策略,从而更加灵活地处理重试问题。

                                          PS:

                                          要注意一个问题,如果项目引入了spring-retry包就算不配置重试GET请求也会默认服务实例切换重试1数使用的是Ribbon的配置,

                                          spring-retry包这里使用的是RetryableFeignLoadBalancer来进行的请求调用和重试处理,在RetryableFeignLoadBalancer.execute()方法中可以看到会去封装一个RetryTemplate,这个RetryTemplate中会设置一个RetryPolicy,如果没有单独配置重试就会使用Ribbon的RibbonLoadBalancedRetryPolicy,在RibbonLoadBalancedRetryPolicy中的变量RibbonLoadBalancerContext里就能看见是如果使用Ribbon配置来进行的重试次数判断

                                          九、OpenFeign请求日志级别配置

                                          • Feign远程调用时提供了日志打印功能,输出的日志级别为debug,先要把项目的输出日志设置为debug。

                                            // 设置指定日志输出级别为debug,我这里将我自己com.kerwin包所有日志输出级别设置成了debug,根据自己需要来即可
                                            logging:
                                              level:
                                                com:
                                                  kerwin: debug
                                            
                                          • 为了更加精细化控制日志输出,Feign还提供了日志内容输出的几个级别。

                                            NONE:默认的,不显示任何日志。

                                            BASIC:仅记录请求方法和URL以及响应状态代码和执行时间。

                                            HEADERS:除了BASIC中定义的信息之外,还有请求和响应头的信息。

                                            FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。

                                            1、通过配置文件配置(推荐)

                                            feign:
                                              client:
                                                config:
                                                  # 默认配置 如果不单独配置每个服务会走默认配置
                                                  default:
                                                    loggerLevel: FULL # 日志级别 NONE:不打印  BASIC:打印简单信息 HEADERS:打印头信息 FULL:打印全部信息 (默认 NONE)
                                                  # 配置单独FeignClient 
                                                  # @FeignClient(value = "kerwin-user",contextId = "userInfoClient")
                                                  # 如果FeignClient注解设置了contextId这里就使用userInfoClient如果没有设置contextId就直接使用服务名称kerwin-user
                                                  userInfoClient:
                                                    loggerLevel: FULL # 日志级别 NONE:默认不打印  BASIC:打印简单信息 HEADERS:打印头信息 FULL:打印全部信息(默认 NONE)
                                            

                                            2、使用@FeignClient配置

                                            public class UserInfoClientSpecification  {
                                                @Bean
                                                public Logger.Level feignLoggerLevel() {
                                                    return Logger.Level.FULL;
                                                }
                                            }
                                            
                                            • 在@FeignClient中添加configuration
                                              @FeignClient(value = "kerwin-user",contextId = "userInfoClient",configuration = UserInfoClientSpecification.class)
                                              

                                              十、OpenFeign拦截器

                                              拦截器是OpenFeign可用的一种强大的工具,它可以被用来在请求和响应前后进行一些额外的处理

                                              1、通过OpenFeign请求拦截器设置统一请求头

                                              public class MyHeaderInterceptor implements RequestInterceptor {
                                                  private static String headerName = "token";
                                                  @Override
                                                  public void apply(RequestTemplate requestTemplate) {
                                                      // 在这里添加额外的处理逻辑,添加请求头
                                                      RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
                                                      if (requestAttributes instanceof ServletRequestAttributes) {
                                                          ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
                                                          HttpServletRequest request = attributes.getRequest();
                                                          String value = request.getHeader(headerName);
                                                          requestTemplate.header(headerName, value);
                                                      }
                                                  }
                                              }
                                              

                                              在配置文件中添加拦截器配置

                                              feign:
                                                client:
                                                  config:
                                                    # 默认配置 如果不单独配置每个服务会走默认配置
                                                    default:
                                                      request-interceptors:
                                                        - com.kerwin.config.MyHeaderInterceptor 
                                                    # 配置单独FeignClient 
                                                    # @FeignClient(value = "kerwin-user",contextId = "userInfoClient")
                                                    # 如果FeignClient注解设置了contextId这里就使用userInfoClient如果没有设置contextId就直接使用服务名称kerwin-user
                                                    userInfoClient:
                                                      request-interceptors:
                                                        - com.kerwin.config.MyHeaderInterceptor 
                                              

                                              除了在配置文件中配置同时也能在配置类中配置,不过要注意加载优先级问题,推荐使用配置文件。

                                              十一、替换OpenFeign默认HTTP请求框架HttpURLConnection

                                              1、替换成HttpClient

                                              添加POM

                                              
                                              	io.github.openfeign
                                              	feign-httpclient
                                              
                                              

                                              添加配置

                                              feign:
                                                httpclient:
                                                  enabled: true # HttpClient的开关
                                                  max-connections: 200  # 线程池最大连接数
                                                  max-connections-per-route: 50   # 单个请求路径的最大连接数
                                              

                                              2、替换成OkHttp

                                              添加POM

                                              
                                                  io.github.openfeign
                                                  feign-okhttp
                                              
                                              

                                              添加配置

                                              feign:
                                                httpclient:
                                                  max-connections: 200 # 线程池最大连接数
                                                okhttp:
                                                  enabled: true
                                              

                                              3、源码断点查看是否生效

                                              调用的是在 FeignLoadBalancer.execute() 方法中,断点可以看到request的client为OkHttpClient,如果不替换就是HttpURLConnection

                                              SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign),在这里插入图片描述,第9张

                                              十二、OpenFeign配置hystrix

                                              在配置文件中开启hystrix默认是关闭的

                                              feign:
                                                # 是否开启hystrix 默认false
                                                hystrix:
                                                  enabled: true
                                              

                                              1、使用@FeignClient中的fallback进行回调(不能控制不同异常的处理逻辑)

                                              实现自己对应的FeignClient接口,重写其中的方法,该类一定要能被Spring扫描到,不然无法加载

                                              @Component
                                              public class UserInfoClientFallback implements UserInfoClient {
                                                  @Override
                                                  public String getInfo(Long id) {
                                                      return "服务繁忙";
                                                  }
                                              }
                                              

                                              将fallback配置到@FeignClient中

                                              @FeignClient(value = "kerwin-user",contextId = "userInfoClient",fallback = UserInfoClientFallback.class)
                                              public interface UserInfoClient {
                                                  /**
                                                   * 获取用户信息
                                                   */
                                                  @GetMapping("/user-info/info/{id}")
                                                  String getInfo(@PathVariable("id") Long id);
                                              }
                                              

                                              当调用对应方法出现异常时则会自动回调UserInfoClientFallback 中重写的方法。

                                              2、使用@FeignClient中的fallbackFactory进行回调(可以控制对应异常的处理逻辑)

                                              使用@FeignClient中的fallbackFactory需要自己去实现FallbackFactory接口,泛型使用自己对应UserInfoClient,我们实现FallbackFactory接口的create方法可以看到入参是一个Throwable,我们可以对异常类型和异常信息进行对应的处理,该类也一定要能被Spring扫描到,不然无法加载。

                                              PS:要实现的是feign.hystrix.FallbackFactory别实现了org.springframework.cloud.openfeign.FallbackFactor

                                              @Component
                                              public class UserInfoClientFallbackFactory  implements FallbackFactory {
                                                  @Override
                                                  public UserInfoClient create(Throwable cause) {
                                                      UserInfoClient userInfoClient = new UserInfoClient() {
                                                          @Override
                                                          public String getInfo(Long id) {
                                                              cause.printStackTrace();
                                                              return "异常msg=" + cause.getMessage();
                                                          }
                                                      };
                                                      return userInfoClient;
                                                  }
                                              }
                                              

                                              将我们实现的FallbackFactory配置到@FeignClient中

                                              @FeignClient(value = "kerwin-user",contextId = "userInfoClient",fallbackFactory = UserInfoClientFallbackFactory.class)
                                              public interface UserInfoClient {
                                                  /**
                                                   * 获取用户信息
                                                   */
                                                  @GetMapping("/user-info/info/{id}")
                                                  String getInfo(@PathVariable("id") Long id);
                                              }
                                              

                                              十三、配置Ribbon负载均衡算法

                                              1、单独配置某个服务负载均衡算法(使用全局配置无效)

                                              通过配置文件给Ribbon配置负载均衡算法只能单独给某个服务配置,和SpringBoot集成默认的负载均衡算法为ZoneAvoidanceRule,使用Ribbon的全局配置是无效的,下面会解释为什么。

                                              # 给kerwin-user服务单独配置随机负载均衡算法
                                              kerwin-user:
                                                ribbon:
                                                  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 随机算法
                                              
                                              • Ribbon已经实现的主要有7种负载均衡策略
                                                • RoundRobinRule: 轮询的方式 (在单独使用Ribbon时默认使用该算法)
                                                • RandomRule: 随机方式。
                                                • WeightedResponseTimeRule: 根据响应时间来分配权重的方式,响应的越快,分配的值越大。
                                                • BestAvailableRule: 选择并发量最小的方式。
                                                • RetryRule: 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server。
                                                • ZoneAvoidanceRule: 根据性能和可用性来选择 (在与SpringBoot集成时Ribbon默认使用该算法)
                                                • AvailabilityFilteringRule: 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值)。

                                                  2、为什么使用配置文件配置Ribbon的全局负载均衡算法会无效?

                                                  要探究这个问题需要看一下Ribbon和SpringBoot是如何集成的,和OpenFeign其实也类似,Ribbon会给每个服务名称都生成一个自己的Spring上下文来管理自己需要的Ribbon的一些组件Bean,比如负载均衡算法组件,Ribbon这里也会提供一个类似FeignClientsConfiguration 的缺省配置类RibbonClientConfiguration ,在RibbonClientConfiguration中的ribbonRule() 方法中可以看到如何加载的负载均衡算法组件。

                                                  SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign),在这里插入图片描述,第10张

                                                  跟进去propertiesFactory.isSet() 方法一直到PropertiesFactory.getClassName() 方法就能看到是如何获取的我们的配置,这里会组装一个key值去环境变量中获取对应的配置值,如果有配置则返回对应的值,如果没有配置则返回空,这个key拼接后为kerwin-user.ribbon.NFLoadBalancerRuleClassName ,正好可以和我们的单独配置对应上,如果是使用全局配置这里是读取不到的。

                                                  SpringCloud OpenFeign 全功能配置详解(一文吃透OpenFeign),在这里插入图片描述,第11张