客户端负载均衡Feign之二:Feign 基本功能介绍&超时设置

一、Ribboon配置

二、重试机制

三、Hystrix配置

四、服务降级配置

五、其他配置

六、日志配置

七、从源码中观察Feign中的可用配置项

8、Feign的timeout配置总结

 

一、Ribboon配置

在Spring cloud Feign中客户端负载均衡是通过Spring cloud Ribbon实现的,所以我们可以直接通过配置Ribbon客户端的方式来自定义各个服务客户端调用的参数。那么我们怎么在Spring cloud Feign中配置Ribbon呢?

全局配置

全局配置方法简单,直接用ribbon.<key>=<value>的方式设置ribbon的默认参数。如下:
#ribbon请求连接的超时时间
ribbon.ConnectTimeout=250
#请求处理的超时时间
ribbon.ReadTimeout=1000
#对所有操作请求都进行重试
ribbon.OkToRetryOnAllOperations=true
#对当前实例的重试次数
ribbon.MaxAutoRetries=1
#对下个实例的重试次数
ribbon.MaxAutoRetriesNextServer=1

指定服务配置

为了有针对性的配置,针对各个服务客户端进行个性化配置方式与使用Spring cloud Ribbon时的配置方式一样的,都采用<client>.ribbon.<key>=<value>的格式进行设置。如下:
hello-service.ribbon.ConnectTimeout=500
hello-service.ribbon.ReadTimeout=1000

二、重试机制

#对所有操作请求都进行重试
ribbon.OkToRetryOnAllOperations=false
#对当前实例的重试次数
ribbon.MaxAutoRetries=1
#对下个实例的重试次数
ribbon.MaxAutoRetriesNextServer=1

结果:

未超时(正常)时,compute服务被调用一次。

超时时,compute服务被调用3次。

三、Hystrix配置

在Spring cloud Feign中,除了引入Spring cloud Ribbon之外,还引入了服务保护与容错的工具Hystrix。默认情况下, Spring cloud Feign会为将所有Feign客户端的方法都封装到Hystrix命令中进行服务保护。

也就是说Feign接口调用分两层,Ribbon的调用Hystrix调用,理论上设置Ribbon的时间即可,但是Ribbon的超时时间和Hystrix的超时时间需要结合起来,按照木桶原则最低的就是Feign的超时时间,建议最好配置超时时间一致

 

 

那么在Spring cloud Feign如何配置Hystrix的属性以及如何实现服务降级?
全局配置
全局配置通ribbon一样,直接使用它的默认配置前缀hystrix.command.default就可以进行设置,
在设置之前,需要确认feign.hystrix.enabled参数是否设置为false,如果为false则关闭Feign客户端的Hystrix支持。
或者使用hystrix.command.default.execution.timeout.enabled=false来关闭熔断功能。
比如设置全局的超时时间:
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
详细参数列表见《服务容错保护断路器Hystrix之二:Hystrix工作流程解析》中的《2.8、关于配置》

指定服务配置

如果想局部关闭Hystrix,需要通过使用@Scope("prototype")注解为指定的客户端配置Feign.Builder实例,详细步骤如下:

package com.dxz.feign;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;

import feign.Feign;

@Configuration
public class DisableHystrixConfiguration {

    @Bean
    @Scope("prototype")
    public Feign.Builder feignBuilder() {
        return Feign.builder();
    }
}
package com.dxz.feign.remote;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.dxz.feign.DisableHystrixConfiguration;

//@FeignClient("compute-service")
@FeignClient(name="compute-service",configuration=DisableHystrixConfiguration.class)
public interface HelloService {
    
    @RequestMapping(value="/add", method = RequestMethod.GET)
    String hello(@RequestParam("a") Integer a, @RequestParam("b") Integer b, @RequestParam("sn") Integer sn);

}

结果:

 下面的超时,不会熔断调用fallback方法,而是等待。

四、服务降级配置

新增一个服务降级处理类:

package com.dxz.feign.remote;

import org.springframework.stereotype.Service;

@Service
public class HelloServiceFallback implements HelloService {

    @Override
    public String hello(Integer a, Integer b, Integer sn) {
        System.out.println("HelloServiceFallback");
        return "fallback";
    }

}

在服务绑定接口HelloService中,通过@FeignClient注解的fallback属性来指定服务降级处理类:

package com.dxz.feign.remote;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.dxz.feign.DisableHystrixConfiguration;

@FeignClient(name="compute-service", fallback=HelloServiceFallback.class)
//@FeignClient(name="compute-service",configuration=DisableHystrixConfiguration.class)
public interface HelloService {
    
    @RequestMapping(value="/add", method = RequestMethod.GET)
    String hello(@RequestParam("a") Integer a, @RequestParam("b") Integer b, @RequestParam("sn") Integer sn);

}

测试:

五、其他配置

请求压缩
Spring cloud Feign支持请求与响应的GZIP压缩,以减少通讯过程中的性能损耗。只需要通过下面两个参数设置,就能开启请求与响应的压缩功能:
feign.compression.request.enabled=true
feign.compression.response.enabled=true

六、日志配置

Spring cloud Feign在构建被@FeignClient修饰的服务客户端时,会为每一个客户端创建一个feign.Logger实例,我们可以利用该日志对象的DEBUG模式来帮助分析Feign的请求细节。
开启方式:
logging.level.<FeignClient>=<LEVEL value>

logging.level.com.dxz.feign.remote.HelloService=DEBUG

但是,只添加了如上配置,还无法实现对DEBUG日志的输出,这是由于Feign客户端默认的Logger.LEVEL对象定义为NONE级别。该级别不吉利任何Feign调用过程中的信息,所以需要调整级别,针对全局日志调整,直接在启动类里调整如下,

package com.dxz.feign;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.context.annotation.Bean;

import feign.Logger;

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class ConsumerApplication {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

如果是局部调整,可以为日志级别增加配置类,如下:

package com.dxz.feign;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import feign.Logger;

@Configuration
public class FullLogConfiguation {

    @Bean
    Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }
    
}
package com.dxz.feign.remote;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.dxz.feign.DisableHystrixConfiguration;
import com.dxz.feign.FullLogConfiguation;

//@FeignClient(name="compute-service", fallback=HelloServiceFallback.class)
//@FeignClient(name="compute-service",configuration=DisableHystrixConfiguration.class)
@FeignClient(name="compute-service", fallback=HelloServiceFallback.class, configuration=FullLogConfiguation.class)
public interface HelloService {
    
    @RequestMapping(value="/add", method = RequestMethod.GET)
    String hello(@RequestParam("a") Integer a, @RequestParam("b") Integer b, @RequestParam("sn") Integer sn);

}

测试结果:

 

==================================================

七、从源码中观察Feign中的可用配置项

在Feign的配置类FeignClientFactoryBean.java中的这段代码里,加载的配置项有3次机会,一次一次的覆盖配置项,也就是越具体(比方指定服务名称)的配置就是最终生效的,也就是或定制化配置不同服务的目的。

protected void configureFeign(FeignContext context, Feign.Builder builder) {
        FeignClientProperties properties = applicationContext.getBean(FeignClientProperties.class);
        if (properties != null) {
            if (properties.isDefaultToProperties()) {
                configureUsingConfiguration(context, builder);                                               //配置1
                configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder); //配置2
                configureUsingProperties(properties.getConfig().get(this.name), builder);                     //配置3
            } else {
                configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()), builder);
                configureUsingProperties(properties.getConfig().get(this.name), builder);
                configureUsingConfiguration(context, builder);
            }
        } else {
            configureUsingConfiguration(context, builder);
        }
    }

一、这些配置信息(配置项)来源哪里?

FeignClientProperties.java

@ConfigurationProperties("feign.client")
public class FeignClientProperties

 也就是说feign.client开头的

//配置1 读取的feign包中默认的值,拿timeout来说,来源于feign.Request$Options.java的值(如果你的项目中没有覆盖的话)

    public Options() {
      this(10 * 1000, 60 * 1000);
    }

timeout的默认值分别是10s和60s。要覆盖这个默认值也简单,在RequestInterceptor的继承类中增加:

    @Bean
    Request.Options feignOptions() {
        return new Request.Options(60 * 1000, 60 * 1000);
    }

    @Bean
    Retryer feignRetryer() {
        return Retryer.NEVER_RETRY;
    }

    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.FULL;
    }

//配置2 properties.getDefaultConfig(),这个其实就是FeignClientProperties中的default了,也就是:

feign.client.config.default.connectTimeout: 1000
feign.client.config.default.readTimeout: 1000

 //配置3 this.name,这个是模块名了例如被调用的模块tag

feign.client.config.tag.connectTimeout: 1000
feign.client.config.tag.readTimeout: 1000

配置的详细内容有:

    protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {
        if (config == null) {
            return;
        }

        if (config.getLoggerLevel() != null) {                                    //执行配置1
            builder.logLevel(config.getLoggerLevel());
        }

        if (config.getConnectTimeout() != null && config.getReadTimeout() != null) {                    //执行配置2         
            builder.options(new Request.Options(config.getConnectTimeout(), config.getReadTimeout()));
        }

        if (config.getRetryer() != null) {                                                                //执行配置3
            Retryer retryer = getOrInstantiate(config.getRetryer());
            builder.retryer(retryer);
        }

        if (config.getErrorDecoder() != null) {                                                           //执行配置4
            ErrorDecoder errorDecoder = getOrInstantiate(config.getErrorDecoder());
            builder.errorDecoder(errorDecoder);
        }

        if (config.getRequestInterceptors() != null && !config.getRequestInterceptors().isEmpty()) {      //执行配置5
            // this will add request interceptor to builder, not replace existing
            for (Class<RequestInterceptor> bean : config.getRequestInterceptors()) {
                RequestInterceptor interceptor = getOrInstantiate(bean);
                builder.requestInterceptor(interceptor);
            }
        }

        if (config.getDecode404() != null) {                                                               //执行配置6
            if (config.getDecode404()) {
                builder.decode404();
            }
        }
    }

//执行配置2可知connectTimeout和readTimeout需要成对配置,否则不生效

8、Feign的timeout配置总结

大体上有两种方案,一种是通过设置 ribbon 的超时时间(因为 Feign 是基于 ribbon 来实现的,所以通过 ribbon 的超时时间设置也能达到目的),一种是直接设置 Feign 的超时时间,我将会在下边的篇幅里分别说一下如何通过application.yml 配置文件来设置超时时间。(注:这里是以 Feign 的默认客户端(Client.Default)来说的,如果重写了Feign的客户端,有可能导致部分配置不能生效)

1、ribbon的配置

对于 ribbon 又分为全局配置和指定服务配置:

1.1、全局配置
对所有的服务该配置都生效

ribbon:  
    ReadTimeout: 30000 #单位毫秒
    ConnectTimeout: 30000 #单位毫秒

1.2、指定服务配置
下边代码中的 annoroad-beta 是服务的名称,意思是该配置只针对名为 annoroad-beta 的服务有效,根据实际的需要替换成你自己的服务名

tag:
  ribbon:
    ReadTimeout: 30000 #单位毫秒
    ConnectTimeout: 30000 #单位毫秒

与 Ribbon 一样,

2、Feign 也分为全局配置和指定服务配置:

2.1、全局配置
下边代码中使用的 feign.client.config.default ,意思是所有服务都采用该配置

feign.client.config.default.connectTimeout: 1000  #这两个要成对配置,否则无效
feign.client.config.default.readTimeout: 1000     #这两个要成对配置,否则无效

2.2、指定服务配置

下边代码中使用的 feign.client.config.annoroad-beta,意思是该配置只针对名为 annoroad-beta 的服务有效,可以根据实际的需要替换成你自己的服务名

feign.client.config.tag.readTimeout: 120000     #这两个要成对配置,否则无效
feign.client.config.tag.connectTimeout: 120000 #这两个要成对配置,否则无效

总结:

  • 如果同时配置了Ribbon、Feign,那么 Feign 的配置将生效
  • Ribbon 的配置要想生效必须满足微服务相互调用的时候通过注册中心,如果你是在本地通过 @FeignClient 注解的 url 参数进行服务相互调用的测试,此时 ribbon 设置的超时时间将会失效,但是通过 Feign 设置的超时时间不会受到影响(仍然会生效)
  • 综上所述建议使用 Feign 的来设置超时时间

参考:

https://blog.csdn.net/yangchao1125/article/details/104410068

https://blog.csdn.net/xldmx/article/details/106557516

posted on 2017-09-14 11:39  duanxz  阅读(3530)  评论(0编辑  收藏  举报