前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >微服务服务间调用组件Feign使用介绍、原理、优化技巧

微服务服务间调用组件Feign使用介绍、原理、优化技巧

作者头像
青山师
发布2023-10-17 14:16:38
2.9K0
发布2023-10-17 14:16:38
举报

微服务服务间调用组件Feign使用介绍、原理、优化技巧

Feign是一个声明式的Web Service客户端。它让微服务之间的调用变得更简单。Feign具有可插拔式的注解支持,包括Feign 注解和JAX-RS注解。Feign还支持可插拔的编码器和解码器。Spring Cloud增加了对Spring MVC注解的支持,并且也支持Spring WebFlux。

Feign可以与Eureka和Ribbon组合使用以支持负载均衡。

内容介绍

本文的主要内容主要内容包括:

  1. Feign的基本概念、原理与使用。Feign采用声明式的接口,自动拼接URL、添加参数等工作,简化HTTP客户端的开发。
  2. Feign的高级特性,如日志、压缩、重试、监听器、故障处理等。这些特性使Feign成为一个强大的客户端。
  3. Feign的性能优化,包括连接池、HTTP客户端更换、超时设置、GZIP压缩等。这些措施可以大幅提高Feign的性能。
  4. Feign的高可用方案。通过与服务注册中心、熔断器、链路追踪等组件结合,保障Feign服务的高可用。
  5. Feign的最佳实践。通过超时设置、降级处理、日志级别控制等最佳实践,合理使用Feign。
  6. Feign的源码分析。解析Feign的核心组件如Feign类、Contract接口、Client接口、Logger等,理解Feign的内部机制。
  7. Feign与OpenFeign的区别。OpenFeign在Feign的基础上做了大量增强,更贴近Spring Cloud体系,所以Spring Cloud较偏向OpenFeign。
  8. Feign与RestTemplate的对比。在Spring Cloud体系下,Feign比RestTemplate更适合作为HTTP客户端。
  9. Feign常见问题解答。对Feign使用中常见的问题如调用404、超时、注解不生效等进行解答。

Feign的使用

  1. 添加Feign依赖
代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
  1. 创建接口并添加@FeignClient注解
代码语言:javascript
复制
//定义一个Feign接口
@FeignClient("eureka-client") 
public interface ComputeClient {
   @GetMapping("/add")
   int add(@RequestParam(value = "a") int a, @RequestParam(value = "b") int b);
}

@FeignClient("eureka-client")指定调用的服务名称,这里调用注册在Eureka中的eureka-client服务。

  1. @FeignClient中的接口使用Spring MVC注解来声明Web Service请求
  2. 在主启动类添加@EnableFeignClients注解开启Feign的功能
代码语言:javascript
复制
@SpringBootApplication
@EnableFeignClients 
public class Application {
   public static void main(String[] args) {
      SpringApplication.run(Application.class, args);
   }
}
  1. 调用Feign接口的方法即可完成服务调用
代码语言:javascript
复制
@Autowired
private ComputeClient computeClient;

public int add(int a, int b) {
   return computeClient.add(a, b);  
}

调用computeClient.add(a, b)便可完成对eureka-client服务的调用。

Feign内部集成了Ribbon,所以以上的例子也具有客户端负载均衡的功能。

Feign的执行流程概述

Feign在调用其他服务时,会根据注解中的url进行请求转发。它的工作原理是:

  1. 根据@FeignClient的value值找到服务,如果配置了服务注册中心,还会根据服务名获取服务实例列表。
  2. 构造请求参数,根据方法上的注解(如@PathVariable、@RequestParam、@RequestBody等)确定请求参数。
  3. 根据注解(@GetMapping、@PostMapping、@DeleteMapping等)确定请求方法和路径。
  4. 发起请求调用其他服务,获取结果。
  5. 封装结果,返回给调用方。

Feign的定制化

Feign提供了多种定制化手段:

  1. 日志级别:Feign支持日志记录,我们可以设置日志级别以查看Feign的调用详情。
代码语言:javascript
复制
feign:
  client: 
    config:
      default: 
        loggerLevel: FULL 

设置为ALL,则会展示完整的请求、响应日志详情。

  1. 编码器和解码器:Feign默认使用JSON进行编码和解码,我们可以设置自定义的编解码器。
代码语言:javascript
复制
@Component
public class CustomEncoder implements Encoder {
    //...
}

@Bean
public Encoder feignEncoder() {
    return new CustomEncoder();
}
  1. Feign拦截器:我们可以通过实现RequestInterceptorResponseInterceptor接口来定制拦截请求和响应。
代码语言:javascript
复制
@Bean 
public RequestInterceptor requestInterceptor() { 
  return template -> {
     //自定义逻辑...
     template.header("Content-Type", "application/json");
   }; 
}
  1. Feign接口的继承:我们的Feign接口可以继承另一个接口,这样我们可以重写父接口的方法来实现特定的定制需求。
代码语言:javascript
复制
@FeignClient("some-service")
public interface SomeServiceClient extends SomeService {
    @Override
    @GetMapping("/{path}")
    String doSomething(@PathVariable("path") String path);
}
  1. Contract契约:Spring Cloud Feign默认使用SpringMvcContract,我们可以实现自定义契约来控制Feign的一些行为,例如路径、请求方法等。

Feign的运维实践

在实际项目中使用Feign也会遇到一些问题,这里给出一些运维方面的实践建议:

  1. Feign默认使用JDK原生的URLConnection发送HTTP请求,我们可以选择更高性能的HTTP客户端,如Apache HTTP Client、OkHttp等。
代码语言:javascript
复制
feign:
  client: 
    config:
      default: 
        httpClient:
          enabled: true
          connectionTimeout: 5000
          followRedirects: true 
        loggerLevel: full
        okhttp:
          enabled: true # 开启OkHttp
  1. 超时设置:Feign客户端的超时设置包括:
  • connectTimeout:连接超时,默认10秒。
  • readTimeout:读取超时,默认60秒。

我们可以根据服务调用的耗时情况进行设置,避免超时。

关于feign默认配置参数可以参看FeignClientConfiguration源码类的字段属性。

  1. 服务降级:当服务调用失败或超时,我们需要有降级策略,避免影响整体系统。我们可以为Feign接口增加fallback指定降级实现类。
代码语言:javascript
复制
@FeignClient(value = "compute-service", fallback = ComputeClientFallback.class)
public interface ComputeClient {
    //..
}

public class ComputeClientFallback implements ComputeClient {
    public int add(int a, int b) {
        return -1; 
    }
} 

当compute-service服务调用失败时,会调用ComputeClientFallback里的方法作为降级返回。

  1. 重试:我们可以为Feign增加重试设置,在服务调用失败时进行重试,避免故障扩散。
代码语言:javascript
复制
feign:
  client:
    retry:
      enabled: true # 开启重试
      maxAttempts: 5 # 最大重试次数
      period: 5000    # 重试间隔时间

参看源码类:feign.Retryer.Default

  1. 负载均衡:Feign默认集成Ribbon进行客户端负载均衡,我们可以设置一些Ribbon相关参数进行配置,如连接超时、重试等。
代码语言:javascript
复制
ribbon: 
  ConnectTimeout: 1000 # 连接超时时间
  ReadTimeout: 3000 # 读取超时时间
  OkToRetryOnAllOperations: true # 是否对所有的请求方法都重试
  MaxAutoRetriesNextServer: 1 # 切换实例后的最大重试次数

Feign常见问题排查

  1. Feign调用返回404

这通常是因为Feign调用的url不正确导致的。我们可以:

  • 确认@FeignClient中的value值正确,对应服务注册中心中的服务名。
  • 确认url中的路径正确,可以打印Feign的日志查看实际请求路径。
  • 如果服务实例很多,还需要确认Ribbon的负载均衡策略是否导致请求到错误实例。
  1. Feign调用返回超时

这种情况通常有两种原因:

  • 服务提供方处理时间过长,超出Feign的读超时时间。我们需要适当增加Feign的readTimeout
  • 服务调用链路过长,中间某个服务处理时间过长,导致Feign总调用时间超出。这种情况下需要检查各个链路的处理时间,进行优化。
  1. 服务降级没有生效

这种情况主要有两种原因:

  • @FeignClient没有指定fallback属性,没有降级实现类。这种情况下需要添加降级类。
  • 降级类没有在Spring容器中,没有被扫描到。这种情况需要确认降级类是否被@Component注解,或者增加@FeignClientfallbackFactory指定工厂类来生成降级类实例。
  1. Feign日志级别设置无效

这是因为Feign的日志级别设置有两种方式,且优先级不同:

  • 通过logging.level.xx=DEBUG设置Feign的日志级别,优先级高。
  • 通过feign.Client.config.loggerLevel=FULL设置,优先级低。

所以如果两种方式同时设置,logging.level.xx=DEBUG的设置会覆盖feign.Client.config.loggerLevel=FULL。我们只需要设置一种方式即可,且优先使用logging.level.xx=DEBUG设置Feign日志级别。

  1. 自定义Client不生效?
  • 检查feign.client.name是否设置,且与ClientName一致。
  • 检查@FeignClient(“name”)的name属性是否正确,对应feign.client.name。
  • 检查Client实现类是否加了@Component注解,是否已正确注入Spring容器。
  1. 想使用Spring MVC注解,但不生效?
  • 确认是否使用的OpenFeign,因为OpenFeign才支持Spring MVC注解。
  • 检查OpenFeign的版本是否过低,低版本OpenFeign的注解支持不完整。
  • 检查方法与参数上是否都有相应的注解,某个注解缺失会导致不生效。
  1. 如何给Feign的Bean添加拦截器、AOP等?
  • 由于FeignClient通过JDK代理产生,无法直接为其Bean添加拦截器、AOP等。
  • 可以通过在FeignClient定义的接口上添加拦截器注解的方式给FeignClient添加拦截器。
  • 也可以在FeignClient的实现类上(默认是Feign.Default)添加AOP等。
  • 定义自己的Feign拦截器@Component并配置到feign.client.config.defaultInterceptors。
  1. Feign如何实现文件上传?
  • 文件上传需要使用多部分表单,Feign默认的表单编码器FormEncoder不支持。
  • 需要添加对multipart/form-data的支持,需要引入feign-form的依赖。
  • 并在feign.clients.default.encoders添加MULTIPART_FORM_ENCODER。
  • 然后就可以定义接收MultipartFile的Feign接口进行文件上传了。
  1. Feign可以调用HTTPS接口吗?

可以。Feign默认的Client就支持HTTPS,只需要在@FeignClient的url指定https协议和证书相关配置即可。也可以采用ApacheHttpClient替换默认Client,实现更复杂的HTTPS调用方案。

  1. Feign支持Form表单提交吗?

支持。Feign可以通过SpringMVC的@RequestParam注解传递Form表单值,也支持请求类型application/x-www-form-urlencoded。在Feign Client的方法上使用@RequestMapping(method = RequestMethod.POST)和@RequestParam即可完成Form提交。

这些就是使用Feign过程中常见的几个问题以及解决思路,通过理解Feign的原理加上踩坑经验,可以更好地运维Feign,使其应用更加稳定。

Feign性能优化

作为一个服务调用组件,Feign的性能也是我们需要考虑的点。这里给出一些优化Feign性能的建议:

  1. 选择更高性能的HTTP客户端:如前所述,替换Feign默认的URLConnection,选择Apache HTTP Client、OkHttp等更高性能的HTTP客户端。
  2. 连接池优化:
  • 合理设置连接池大小,不宜太大也不宜太小。
  • 选择支持连接池复用的HTTP客户端,如OkHttp。
  • Ribbon也有连接池设置,与Feign的HTTP客户端配合优化。
  1. 超时优化:
  • 合理设置Feign的连接超时和读取超时。连接超时不宜太长,读取超时根据服务调用耗时设置。
  • Ribbon也有相应超时设置,与Feign协同优化。
  1. 服务线程池优化:

Feign使用JDK默认线程池,我们可以进行定制:

代码语言:javascript
复制
@Bean
public ExecutorService feignExecutorService(){
    // 设置核心线程数,最大线程数,队列大小,释放资源时的延迟时间
    return new ThreadPoolExecutor(xx, yy, zz, TimeUnit.SECONDS, 
        new LinkedBlockingQueue<Runnable>(zzz), 
        new NamedThreadFactory("feign"));
}
  1. 重试优化:
  • 合理设置Feign的重试次数和时间间隔。
  • 区分对不同服务的重试策略,防止重试过度导致系统资源消耗过大。
  1. 日志级别优化:

Feign的日志虽然可以用于调试,但是日志级别过详细也会对性能产生影响,所以可以根据环境设置不同日志级别:

  • 开发环境:Logger.Level.FULL,方便开发调试。
  • 测试环境:Logger.Level.BASIC,了解大体调用流程。
  • 生产环境:Logger.Level.NONE,关闭日志提高性能。
  1. GZIP压缩:

Feign支持对请求和响应进行GZIP压缩,以提高网络传输性能。我们可以开启:

代码语言:javascript
复制
feign:
  compression: 
    request:
      enabled: true # 开启请求GZIP压缩
    response:
      enabled: true # 开启响应GZIP压缩

以上这些优化方案可以很好地提高Feign的性能,使其更加适合生产环境。但优化总是有代价的,需要根据系统的吞吐量、调用链路等实际情况进行权衡和调优。

Feign 性能测试

对Feign客户端进行、性能和可靠性测试也很重要,这里给出一些测试建议:

  1. 单元测试:

我们可以为Feign接口编写单元测试,调用接口并校验响应结果,保证接口逻辑正确。

代码语言:javascript
复制
@RunWith(SpringRunner.class) 
@SpringBootTest
public class ComputeClientTest {

    @Autowired
    private ComputeClient computeClient;

    @Test
    public void testAdd() {
        int result = computeClient.add(1, 2);
        assertEquals(3, result);
    }
} 
  1. 集成测试:

启动全部服务,然后调用Feign客户端进行端到端测试,校验整体流程和结果正确性。

  1. 压力测试:

使用工具如JMeter对Feign客户端进行大并发调用,测试Feign在高负载下的性能表现是否达标。主要关注:

  • QPS:每秒查询率,需要达到系统预期的QPS。
  • RT:响应时间,需要控制在可接受范围内,不宜太长。
  • 错误率:服务调用错误率需要控制在可接受范围内。
  • 资源消耗:CPU、内存、网络bandwidth等资源消耗是否在可控范围。
  1. 稳定性测试:
  • 长时间高负载测试:模拟高访问量场景,长时间大并发访问Feign客户端,测试其稳定性。
  • 服务故障测试:模拟服务提供方部分实例故障,测试Feign的容灾性和降级是否生效。
  • 网络故障测试:模拟网络抖动、高延迟、短时网络中断等情况,测试Feign的稳定性。

这些测试可以有效检验Feign客户端的健壮性,保证其在复杂环境下仍能稳定运行。我们还可以根据系统的重要程度和流量规模,制定不同的性能指标和可用性指标,对Feign进行严苛的SLA考核。

套用一些流行框架如:

  • Spring Cloud Contract用于微服务contract测试
  • Resilience4j进行熔断、限流、重试等过载防护
  • Hystrix进行熔断和线程隔离

这可以更好保证Feign的高可用。综上,测试和过载防护是保证Feign稳定性的重要一环,希望通过这些测试实践和框架应用,可以让Feign在复杂环境下表现更加可靠。

Feign高可用方案

对于一个微服务系统来说,服务调用是非常重要的一个环节,Feign作为一个重要的调用组件,其高可用性直接影响整个系统的高可用。这里给出一些提高Feign高可用的方案:

  1. 服务发现与注册:

Feign常与Eureka、Consul等服务注册中心协同使用,这些服务注册中心本身也支持集群部署,可以提高Feign服务调用的高可用性。

  1. Ribbon负载均衡:

Feign内置的Ribbon组件,我们可以设置多个服务实例,并选择合适的负载均衡策略,避免单点故障。

  1. Hystrix容错保护:

Hystrix可以进行线程隔离、熔断等策略保护Feign,避免在高并发下服务被过载。熔断机制可以快速失败,避免操作阻塞。

  1. Http客户端连接池:

使用连接池,如Apache HTTP Client、OKHttp等,可以进行连接复用,避免每次调用都建立新的连接。并且这些客户端本身也支持高可用配置,如设置多个Url地址。

  1. 超时与重试机制:

合理设置Feign的连接超时、读取超时时间,可以快速发现服务问题并快速失败,避免资源占用过长时间。配合重试机制,在一定次数后快速返回,防止长时间的不可用服务导致系统不可用。

  1. 熔断与限流:

除Hystrix外,可以使用Resilience4J等开源组件进行更加全面和强大的熔断、限流、重试。限流可以防止突发高流量导致系统不可用。

  1. 服务跟踪:

使用组件如Zipkin进行服务调用链路跟踪,一旦出现高延迟或不可用服务,可以快速定位问题所在。

  1. 降级策略:

为Feign接口指定Fallback以及合适的降级策略,在服务不可用时提供备选方案,避免不可用服务导致依赖服务完全不可用。

  1. 日志记录与监控:

合理配置Feign日志级别,并结合ELK等日志收集工具进行监控。一旦服务有异常状况,可以快速发现并定位问题。

综上,Feign高可用需要多方面的保障和运维,需要与服务注册中心、熔断限流组件、链路跟踪组件、监控日志组件等协同配合,共同提高Feign和依赖其的整个微服务系统的高可用性。

Feign源码分析

理解Feign的源码,有助于我们更深入理解其工作原理,从而合理使用和定制Feign。这里简要分析Feign的源码:

  1. Feign类:Feign类是Feign的入口,主要工作是:
  • 解析Feign注解,获取接口方法与url映射关系,请求类型等信息。
  • 构建ReflectiveFeign类,封装接口方法与请求细节的映射。
  • 构建Feign.Builder,用于创建Feign实例。
  • 创建Logger用于记录Feign日志。
  • 绑定Contract契约,默认是SpringMvcContract。
  1. ReflectiveFeign类:
  • 维护Feign接口方法与请求模板(RequestTemplate)的映射。
  • 调用接口方法时,查找请求模板,使用请求参数构造URL,发起HTTP请求。
  • 将响应结果转换为接口方法 defined 返回类型,返回给调用方。
  1. Contract接口与SpringMvcContract:
  • Contract接口定义了诸如生成请求模板、构造参数值到模板变量等规则。
  • SpringMvcContract实现了Spring MVC注解方式,将方法、参数注解转化为请求模板变量与值。
  1. Client接口与Client.Default实现:
  • Client接口定义了发起HTTP请求的方法。Feign使用构建器模式,允许我们选择不同Client实现来发送请求。
  • Client.Default实现了使用JDK原生URLConnection发送HTTP请求。我们可以实现自定义Client,如使用OKHttp等。
  1. Encoder和Decoder:
  • Encoder负责对请求参数进行编码,默认使用SpringEncoder对参数进行JSON编码。
  • Decoder负责对响应结果进行解码,默认使用SpringDecoder对JSON响应进行解码。
  1. Logger和LoggingInterceptor:
  • Logger定义了记录Feign日志的规范,有4个级别:NONE、BASIC、HEADERS、FULL。
  • LoggingInterceptor拦截Feign请求与响应,将详细信息记录为Feign日志, logfile可指定日志记录位置。
  1. Retryer接口:定义重试策略,Feign内置支持backoff、exponential backoff重试策略。我们也可以自定义Retryer实现。

这些是Feign的主要组成部分, Feign的高效与灵活正是因为这些组件采用接口设计,允许我们灵活选择与替换。理解这些组件的作用与关系,有助于我们使用Feign的源码进行定制化开发。

Feign与OpenFeign区别

Feign和OpenFeign都是Netflix开源的声明式HTTP客户端,但有一定的区别:

  1. 源起:

Feign起源于Netflix,后捐给Spring Cloud并成为Spring Cloud Netflix的一部分。OpenFeign是Spring Cloud对Feign进行增强,成为Spring Cloud的组成部分,目的是提供Spring MVC注解的支持、 wrongly监听机制等新功能。

  1. 注解支持:

Feign仅支持JAX-RS注解,对Spring MVC注解不支持。OpenFeign支持Spring MVC注解,更贴近Spring开发体验,支持内容协商、验证等机制。

  1. 编解码器:

Feign仅支持QueryStringEncoder、FormEncoder和JsonEncoder三种编解码器。OpenFeign内置了SpringEncoder和StringDecoder,支持更丰富的对象与HTTP请求的编解码,如集合、 maps等。

  1. contract:

Feign仅支持接口方法签名与url的映射,请求细节无法定制。OpenFeign支持SpringMvcContract,可以定制请求方法、参数绑定等细节。

  1. 拦截器:

Feign不支持请求与响应拦截器。OpenFeign支持ClientRequestInterceptor和ClientResponseInterceptor,允许拦截并自定义Feign的请求与响应。

  1. 监听支持:

Feign不支持对指标与事件的监听。OpenFeign支持监听连接池大小、请求计数、处理时间等指标,以及连接成功、失败等事件。方便监控Feign运行状态。

  1. 对Cloud致敬:

Feign是Netflix的开源项目,不依赖Spring Cloud。OpenFeign致敬Spring Cloud,与Spring Cloud深度整合,依赖Spring Cloud Context与Spring Boot。

可见,OpenFeign在Feign的基础上进行了大量增强,更加贴近Spring Cloud体系,功能更加丰富完善,维护也更加活跃。所以在Spring Cloud微服务架构下,OpenFeign往往是一个更好的选择。但Feign本身也是一个成熟可靠的HTTP客户端,如果我们对Spring体系不太依赖,直接使用Feign同样是一个好选择。总之,这 still需要根据我们的技术选型和需求来权衡。

Feign与RestTemplate对比

Feign和RestTemplate都是比较常用的HTTP客户端,但有以下主要区别:

  1. 使用方式:

RestTemplate的使用方式比较接近传统HTTP API,需要手动构建URL,拼接参数等:

代码语言:javascript
复制
RestTemplate restTemplate = new RestTemplate();
String url = "http://example.com/users?name={name}";
String result = restTemplate.getForObject(url, String.class, "Jack");

Feign的使用方式是声明式的,只需要定义接口并注解,更加面向接口:

代码语言:javascript
复制
@FeignClient("example.com")
public interface UserClient {
    @GetMapping("/users")
    String getUser(@RequestParam("name") String name);
}
  1. 可维护性:

Feign的接口更加抽象,屏蔽了实现细节,有利于后续维护和替换。RestTemplate的调用方式过于具体,不利于变更。

  1. 可扩展性:

Feign有更加丰富的扩展点,如支持多种编解码器、拦截器、Client等。RestTemplate扩展比较困难。

  1. 支持功能:

Feign内置了负载均衡、重试、监听等机制。而RestTemplate需要与其他组件配合使用才能完成,如Ribbon等。

  1. 与Spring的整合:

Feign是Spring Cloud的一部分,与Spring框架深度整合。而RestTemplate需要自己进行与Spring的整合。

Feign与其它组件的关系、区别

  1. Feign与Ribbon的区别和关系:
  • Feign和Ribbon都是Netflix开源的组件,用于微服务调用。
  • Feign是一个声明式的HTTP客户端,主要负责HTTP请求的发送。
  • Ribbon是一个负载均衡器,主要用于客户端的负载均衡。
  • Feign内部使用Ribbon进行负载均衡,所以当使用Feign时,不需要再单独使用Ribbon。
  • 但我们仍然可以在Feign中配置或替换Ribbon,实现自定义的负载均衡策略。
  1. Feign与Hystrix的区别和关系:
  • Feign和Hystrix也都是Netflix开源的组件,用于微服务体系。
  • Feign是一个HTTP客户端,主要用来发送HTTP请求。
  • Hystrix是一个容错管理工具,主要用来提高微服务的可靠性与容错能力。
  • Feign可以与Hystrix结合,使用Hystrix的容错机制来保护Feign的服务调用。
  • 我们只需要在Feign客户端上添加@FeignClient中的fallback指定容错方法即可启用Hystrix容错。
  • 也可以手动在fallback方法中使用HystrixCommand进行服务降级等操作。
  1. Feign与Zuul的区别和关系:
  • Feign是Netflix的HTTP客户端,用于服务调用。Zuul是Netflix的网关,用于路由转发。
  • 一个微服务体系通常会同时使用Feign和Zuul这两个组件。
  • Feign用于服务内部的调用,Zuul主要用于外部访问系统的统一入口。
  • Zuul可以与Feign结合,将外部访问路由到内部服务,而这些内部服务之间可以使用Feign相互调用。
  • 所以Zuul和Feign虽然功能不同,但可以良好配合,共同支撑起一个微服务架构。

这些内容的理解可以让我们对Feign与其他微服务组件有一个更全面的认知,知晓它们之间的关系与区别,这有助于我们在设计微服务方案时做出更好的选择与组合。Feign虽然功能十分强大,但它通常并不孤立存在,而是与其他组件配合使用,发挥更大的价值。

Feign的高级内容、自定义扩展实践

  1. Feign的继承支持:

Feign天生支持接口的继承,子接口自动继承父接口的方法与注解。这让我们可以设计出层次清晰的Feign接口,如:

代码语言:javascript
复制
public interface HelloService {
  @RequestMapping(method = RequestMethod.GET, value = "/hello")
  String sayHello(); 
}

public interface HelloServiceExtended extends HelloService {
  @RequestMapping(method = RequestMethod.GET, value = "/hi")
  String sayHi();
}

然后在@FeignClient中指定HelloServiceExtended接口,那么FeignClient将同时具有sayHello()sayHi()两个方法。

  1. Feign的请求模板:

有时候我们的Feign方法中存在大量重复的注解与参数,这时可以使用Feign的请求模板进行抽取。如:

代码语言:javascript
复制
@Configuration 
public class FeignConfig {
  @Bean
  public RequestInterceptor basicAuthRequestInterceptor() {
    return template -> {
      template.header("Authorization", "Basic YWRtaW46YWRtaW4=");  
      template.query("limit", 100);
    };
  }
}

然后在@FeignClient中指定这个拦截器:

代码语言:javascript
复制
@FeignClient(name = "example", configuration = FeignConfig.class)
public interface ExampleFeignClient {
 //这个方法将自动继承basicAuthRequestInterceptor中的模板参数    
  @GetMapping("/users") 
  List<User> getUsers();
} 

这样getUsers()方法就不需要再添加limit参数和Authorization header了。

  1. Feign的 Contract 协定:

Feign通过Contract协定将方法映射到请求模板,包括请求绑定、URL映射规则等。Feign提供的Contract有:

  • DefaultContract:简单的约定,将方法名映射到相同的URL,并使用JAXRS注解绑定参数。
  • SpringMvcContract:采用SpringMVC的约定,支持路径变量、请求参数等,更贴近Spring开发风格。
  • HystrixDelegatingContract:在SpringMvcContract的基础上,为每个方法创建一个HystrixCommand,以支持容错。

我们可以通过feign.client.contract指定某个Contract,或者继承Contract进行自定义。Contract让我们可以改变Feign的调用约定,使其适配不同的服务样式。

这里为您介绍了Feign中的继承支持、请求模板、Contract协定等内容。这些内容的理解可以让我们在设计Feign接口与调用方式时,拥有更丰富的选择与能力。Feign虽然简单易用,但其实它的机制相当灵活丰富,满足我们的定制需求。

  1. Feign的拦截器:

Feign支持使用拦截器对其请求进行拦截,我们可以实现以下拦截器:

  • RequestInterceptor:在请求发出之前拦截,可以修改请求。
  • ResponseInterceptor:在得到响应之后拦截,可以修改响应。
  • ErrorInterceptor:在出现异常时拦截,可以修改或重试请求。

可以通过 feign.client.config.defaultInterceptors添加拦截器,如:

代码语言:javascript
复制
feign:
  client:
    config:
      defaultInterceptors:
        - feign.auth.BasicAuthRequestInterceptor  #添加BasicAuth拦截器
        - com.example.logging.LoggingInterceptor  #添加日志拦截器 

也可以为指定的FeignClient添加拦截器:

代码语言:javascript
复制
@FeignClient(name="example", configuration = ExampleFeignConfig.class) 
public interface ExampleFeignClient {
}

@Configuration
class ExampleFeignConfig {
  @Bean
  public RequestInterceptor requestInterceptor() {
    return new BasicAuthRequestInterceptor("username", "password");  
  }  
}

拦截器让我们可以更灵活地定制Feign的行为,实现复杂的请求处理逻辑。

  1. Feign的解码器:

Feign默认使用ResponseEntityDecoder对响应进行解码,将其解码为ResponseEntity<T>对象。我们可以实现自己的解码器,如:

代码语言:javascript
复制
public class ExampleDecoder implements Decoder {
  @Override
  public Object decode(Response response, Type type) {
    if (type == Example.class) {
      //对响应进行定制化解码
      return ... ; 
    } 
  }
}

然后通过feign.client.config.decoder指定这个解码器,如:

代码语言:javascript
复制
feign:
  client: 
    config:
      example:   #指定FeignClient名称
        decoder: com.example.ExampleDecoder

定制的解码器让我们可以对Feign的响应进行转换和定制,以适配服务的响应格式。

  1. Feign的校验器:

Feign也支持Validator校验器,当接收到的响应无法正确解码时,可以使用校验器进行二次校验和处理。我们需要实现Validator接口,并指定给FeignClient使用。

Feign的拦截器、解码器和校验器让我们可以高度定制Feign的请求/响应处理过程,这也是Feign高扩展性的体现。利用这些机制,我们可以轻易地让Feign支持定制的协议格式、复杂的请求/响应流程。这使得Feign不仅仅是一个简单的HTTP客户端,还可以成为一个功能丰富的通用服务调用方案。

  1. Feign的API优化:

随着业务发展,Feign的API会变得越来越庞大,这会带来一定的维护难度。我们可以通过以下方式优化Feign的API:

  • 根据业务模块拆分FeignClient:不要定义一个太庞大的FeignClient,可以根据业务模块拆分为多个FeignClient。
  • 使用接口继承进行抽象:定义一个基础接口,让其他接口继承,抽取公共方法。
  • 利用请求模板减少重复注解:通过Feign的请求模板机制,减少Feign方法中的重复注解。
  • 采用DTO作为方法参数:不要使用基础类型作为Feign方法的参数,改用DTO对象,让方法变得更具语义化。
  • 选择语义化的方法名:给Feign的方法起一个语义化的名字,而不仅仅是URL路径。
  • 抽取公共参数为Constant:如果某些Feign方法存在大量重复的参数,可以把它抽取为一个Constant,方法中只传入这个Constant。
  • 采用Builder模式构造复杂参数:当Feign方法需要一个复杂的参数时,可以使用Builder模式构建这个参数,而不是定义一个过于庞大的参数DTO。

这些优化措施可以让我们的Feign API更清晰、更易维护。Feign虽然简单,但随着业务成长,如果不加以管理,也可以变得难以维护,所以API的优化是一个比较重要的话题。

  1. Feign的可选参数:

Feign默认采用JAXRS注解将方法映射到HTTP请求,但JAXRS注解不支持可选参数。为支持可选参数,我们可以:

  • 给可选参数定义默认值:
代码语言:javascript
复制
@GetMapping("/users")
List<User> getUsers(@RequestParam(value="age", required=false) Integer age); 
  • 使用@Nullable注解标记可选参数:
代码语言:javascript
复制
@GetMapping("/users")
List<User> getUsers(@Nullable @RequestParam Integer age);
  • 在方法中追加一个java.util.Optional<T>类型的参数:
代码语言:javascript
复制
@GetMapping("/users")
List<User> getUsers(Optional<@RequestParam Integer> age); 
  • 采用Builder模式构建参数,可选参数不传入Builder:
代码语言:javascript
复制
@GetMapping("/users") 
List<User> getUsers(UserSearchCriteria criteria);

UserSearchCriteria criteria = UserSearchCriteria.builder()  
                                          .age(30)  
                                          .build();

这些方式可以让我们的Feign接口支持可选参数,变得更加灵活。

  1. Feign的文件上传:

Feign默认不直接支持文件上传,我们有以下方式实现:

  • 使用feign.codec.Encodedecode.MultiPartFormContent编码器:
代码语言:javascript
复制
@PostMapping("/upload")
void uploadFile(@Part("file") RequestBody file); 

然后在FeignClient中添加:

代码语言:javascript
复制
@FeignClient(configuration=MultipartSupportConfig.class)
public interface FileUploadClient {
} 

@Configuration
public class MultipartSupportConfig {
  @Bean
  public Encoder feignEncoder() {
    return new MultipartFormContent();
  }
}  
  • 直接调用Feign的底层客户端进行文件上传:
代码语言:javascript
复制
@PostMapping("/upload")
void uploadFile(); 

public void uploadFile() {
  Client client = ...;  //获取Feign的Client
  Request request = ... //构建文件上传请求
  client.execute(request); 
}
  • 采用Feign的拦截器获取文件,然后手动构建MultiPart请求:
代码语言:javascript
复制
@PostMapping("/upload")
void uploadFile();  

public void uploadFile() {
  File file = ...; //获取要上传的文件
  String uploadUrl = ...; //获取上传URL
  
  //手动构建MultiPart请求
  MultiPartRequest request = new MultiPartRequest(uploadUrl);
  request.addFile("file", file);
  ... ...
} 

这些方式可以让我们的Feign接口支持文件上传的功能,对接那些需要文件上传的服务。

Feign虽然简单易用,但我们也可以通过各种方式进行扩展,实现较为复杂的服务调用需求。

  1. Feign的OAuth2支持:

Feign默认不支持OAuth2,我们可以通过以下方式实现:

  • 自定义RequestInterceptor拦截器,在每个请求加入OAuth2的Authorization头。
  • 扩展Feign的Contract,把OAuth2的Authorization头数据注入到每个请求模板中。
  • 利用Zuul的OAuth2支持,在网关处获得访问令牌,然后把令牌转发给Feign客户端。
  • 直接使用Feign的底层客户端,在执行每个请求前,从OAuth2服务器获取访问令牌,并手动加入到请求头中。

Feign的Hystrix支持

Feign默认提供了对Hystrix的集成支持,我们可以很容易地为Feign接口启用Hystrix。主要有以下方式:

  • 在application.yml中全局指定:
代码语言:javascript
复制
feign:
  hystrix:
    enabled: true
  • 在@FeignClient中指定fallback:
代码语言:javascript
复制
@FeignClient(name = "user", fallback = UserClientFallback.class)
public interface UserClient {
}
  • 实现FallbackFactory:
代码语言:javascript
复制
@Component 
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
  @Override
  public UserClient create(Throwable cause) {
    return new UserClientFallback(cause); 
  }
}

@FeignClient(name = "user", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient { 
}
  • 配置Hystrix的command属性:
代码语言:javascript
复制
feign:
  client:
    config:
      user:  #FeignClient名称
        hystrix:
          command:  #HystrixCommand配置
            default:
              execution.isolation.thread.timeoutInMilliseconds: 1000 
  • 定制Hystrix的线程池、信号量隔离策略:
代码语言:javascript
复制
@Configuration
public class FeignHystrixConcurrencyStrategy {
  @Bean
  public HystrixConcurrencyStrategy feignHystrixConcurrencyStrategy() {
    return hystrixConfig -> {  
      // 定制 Hystrix 线程池策略
      HystrixThreadPoolProperties.Setter()...
      // 定制 Hystrix 信号量隔离策略  HystrixSemaphoreProperties.Setter()... 
    };
  }
}

Feign的Hystrix支持让我们可以方便地对Feign接口应用熔断与容错措施,提高微服务的稳定性。

Feign的实践案例

这里提供一个Feign的最佳实践案例:

代码语言:javascript
复制
@FeignClient(name = "order", url = "http://order-service")
public interface OrderClient {
    @GetMapping("/orders/{id}")
    Order getOrder(@PathVariable("id") Long id);
}

@FeignClient(name = "inventory", url = "http://inventory-service")
public interface InventoryClient {
    @GetMapping("/inventories/{productId}")
    Product getInventory(@PathVariable("productId") String productId);
}

@Service
public class BussinessService {
    @Autowired
    private OrderClient orderClient;
    @Autowired
    private InventoryClient inventoryClient;

    public void purchase(Long orderId, String customer) {
        //查询订单
        Order order = orderClient.getOrder(orderId);
        //查询库存
        Product product = inventoryClient.getInventory(order.getProductId()); 
        //进行采购流程
        //...
    }
}

这是一个简单的采购业务场景,主要有:

  • OrderClient:用于调用Order微服务,提供订单查询方法。
  • InventoryClient:用于调用Inventory微服务,提供库存查询方法。
  • BussinessService:提供采购业务逻辑,通过Feign Client调用Order和Inventory服务。
  • 这里OrderClient和InventoryClient声明为Feign Client,通过url指定服务地址,采用标准的SpringMVC注解。
  • BussinessService可以像调用本地方法一样,轻松调用这些Feign Client。
  • 这些Feign Client天然支持Ribbon负载均衡,我们不需要额外配置。
  • 也可以轻易为这些Feign Client启用Hystrix熔断机制。

这是一个典型的Feign最佳实践案例,采用Feign实现微服务之间的调用,既简单又优雅。

Feign的线程模型

Feign默认采用SimpleClient的线程模型,主要特征如下:

  • 一个Client只有一个线程,所有的请求串行执行。
  • 该线程同时也负责发起连接,传输响应等任务。
  • 该线程会在空闲时进入阻塞,减少资源占用。
  • 该线程 daemon状态,随应用关闭而关闭。

这种简单的线程模型,可以有效减少线程切换与管理 overhead,提高性能。但也存在几个问题:

  • 一个慢请求会阻塞其他请求,影响整体延迟。
  • 无法充分利用高并发环境下的资源。
  • 难以调优,线程利用率不可控。

为此,Feign也提供了其他线程模型:

  • HystrixFeign:通过Hystrix线程池提供隔离,避免一个慢请求影响其他请求,提高容错性。
  • 线程池Feign:使用自定义线程池,可以配置线程数量,提高并发性和控制延迟,利用资源更优。

可以通过设置feign.client.config.default 配置项来选择线程模型。

选择合适的线程模型,可以显著优化Feign的性能表现,提高资源利用率和容错性。但也需要权衡相关配置的复杂性。

Feign的连接池

Feign默认也使用连接池,主要特征如下:

  • 默认最大连接数为200。
  • 连接空闲时间默认为60秒,超时则关闭。
  • 默认支持100个连接打开时间超过30s的长时间连接。
  • 连接池使用 CommonsPool实现,简单可靠。

我们可以通过feign.client.config配置连接池参数,如:

代码语言:javascript
复制
feign:
  client: 
    config:
      default: 
        pool: 
          maxTotal: 500     #最大连接数
          maxPerRoute: 50   #每个路由最大连接数  
          validateAfterInactivity: 1s #连接空闲校验时间      

连接池的配置与优化,也是提高Feign性能和稳定性的重要一环。

Feign的HTTP连接管理

Feign对HTTP连接的管理主要包含:

  • 长连接与短连接:Feign默认采用Keep-Alive头,维持长连接,可以配置connectionTimeout指定连接最长存活时间。也支持配置为短连接close。
  • 最大连接数:每个url对应一个连接池,默认最大连接数为200。可以配置maxConnections自定义。
  • 复用连接:对同一主机和连接的请求可以复用连接,实现连接重用。unless连接被关闭。
  • 空闲连接管理:Feign会定期清理超过一定空闲时间的连接。默认60秒。可配置idleTimeout自定义。
  • 失效连接清理:Feign也会定期清理超过最大存活时间的失效连接。默认5分钟。可配置timeToLive自定义。
  • 连接预热:第一次访问url时,Feign会预先与服务建立一定数量的连接,默认为url的最大连接数,实现快速响应。可关闭预热,配置initialLineRequests为0。

这些连接管理机制可以使Feign充分复用连接,合理分配连接资源,实现高效网络 I/O。但是,也需要根据实际场景权衡配置:

  • 长连接时间过长,连接资源可能过度占用。
  • 空闲连接过期时间过短,连接无法充分复用,带来额外开销。
  • 连接预热数量过多,启动阶段延迟较高,并发能力可能受限。
  • 定期清理频率过低,无法快速释放失效连接,导致资源浪费。

所以,Feign的连接管理参数配置需要综合考虑系统稳定性、并发量与延迟指标而定制。

Feign的Encoder和Decoder

Feign默认提供了对JSON的编解码支持,我们也可以通过Encoder和Decoder接口扩展Feign的编解码能力。常见的编解码器包括:

  • ContentTypeDecoder: 根据Content-Type解析响应体,支持多格式。
  • FormEncoder: 编码表单请求,Content-Type为application/x-www-form-urlencoded。
  • XMLDecoder/XMLEncoder: 支持XML格式编解码。
  • GZIPDecoder/GZIPEncoder: 支持GZIP压缩的响应解码和请求编码。
  • CBORDecoder/CBOREncoder: 支持CBOR二进制格式的编解码,性能更高。

扩展Feign的编解码器,可以使其支持更多格式的响应与请求,如XML、Protobuf、CBOR等,实现多样化的服务集成。 我们可以通过两种方式扩展:

  1. 全局配置: 覆盖feign.codec.decoder和feign.codec.encoder属性。
  2. 局部配置: 为某个FeignClient指定decoder和encoder。

例如,配置XML编解码器:

代码语言:javascript
复制
feign:
  client: 
    config:
      default: 
        decoder: feign.codec.xml.XMLDecoder
        encoder: feign.codec.xml.XMLEncoder

对某Client配置:

代码语言:javascript
复制
@FeignClient(name="clientName", decoder = XMLDecoder.class, encoder = XMLEncoder.class)
public interface ClientInterface {
}  

扩展Feign的编解码能力,可以使其不仅支持传统的JSON/XML接口,也可以配合自定义编解码器实现任意格式的接口集成,成为连接各种服务的统一调度入口。

Feign的添加头信息

Feign可以通过以下方式添加请求头信息:

  1. 方法上的@Header注解:
代码语言:javascript
复制
@FeignClient("serviceName")
public interface Client {        
   @RequestMapping(method = RequestMethod.GET, "/hello")
   @Header("token: 1234")   //添加头信息
   String hello();
}
  1. 为FeignClient添加defaultHeaders属性:
代码语言:javascript
复制
@FeignClient(name = "clientName", defaultHeaders = {
   "token: 1234"              
})
public interface Client {  
   //...
}
  1. 配置feign.client.headers:
代码语言:javascript
复制
feign:
  client:
    headers: 
      token: 1234   
  1. 为方法添加HeaderInterceptor:
代码语言:javascript
复制
@FeignClient(name = "clientName")
public interface Client {
   @RequestMapping(method = RequestMethod.GET, "/hello")
   String hello(); 
}

//Header Interceptor
public class HeaderInterceptor implements RequestInterceptor {
   @Override
   public void apply(RequestTemplate template) {
       template.header("token", "1234"); 
   }
}

//为Client接口增加拦截器 
@FeignClient(name = "clientName",interceptors = HeaderInterceptor.class )
public interface Client {  
   // ...
}

Feign提供了多种方式添加请求头,以实现服务鉴权、传递上下文等功能。但是,配置方式较为分散,维护稍显不便。

本文的内容就到此了,我们介绍了很多关于Feign的使用技巧以及优化经验,相信能帮助大家在实际生产项目中正确使用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 微服务服务间调用组件Feign使用介绍、原理、优化技巧
    • 内容介绍
      • Feign的使用
        • Feign的执行流程概述
          • Feign的定制化
            • Feign的运维实践
              • Feign常见问题排查
                • Feign性能优化
                  • Feign 性能测试
                    • Feign高可用方案
                      • Feign源码分析
                        • Feign与OpenFeign区别
                          • Feign与RestTemplate对比
                            • Feign与其它组件的关系、区别
                              • Feign的高级内容、自定义扩展实践
                                • Feign的Hystrix支持
                                  • Feign的实践案例
                                    • Feign的线程模型
                                      • Feign的连接池
                                        • Feign的HTTP连接管理
                                          • Feign的Encoder和Decoder
                                            • Feign的添加头信息
                                            相关产品与服务
                                            负载均衡
                                            负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
                                            领券
                                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档