微服务服务间调用组件Feign使用介绍、原理、优化技巧
Feign是一个声明式的Web Service客户端。它让微服务之间的调用变得更简单。Feign具有可插拔式的注解支持,包括Feign 注解和JAX-RS注解。Feign还支持可插拔的编码器和解码器。Spring Cloud增加了对Spring MVC注解的支持,并且也支持Spring WebFlux。
Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
内容介绍
本文的主要内容主要内容包括:
- Feign的基本概念、原理与使用。Feign采用声明式的接口,自动拼接URL、添加参数等工作,简化HTTP客户端的开发。
- Feign的高级特性,如日志、压缩、重试、监听器、故障处理等。这些特性使Feign成为一个强大的客户端。
- Feign的性能优化,包括连接池、HTTP客户端更换、超时设置、GZIP压缩等。这些措施可以大幅提高Feign的性能。
- Feign的高可用方案。通过与服务注册中心、熔断器、链路追踪等组件结合,保障Feign服务的高可用。
- Feign的最佳实践。通过超时设置、降级处理、日志级别控制等最佳实践,合理使用Feign。
- Feign的源码分析。解析Feign的核心组件如Feign类、Contract接口、Client接口、Logger等,理解Feign的内部机制。
- Feign与OpenFeign的区别。OpenFeign在Feign的基础上做了大量增强,更贴近Spring Cloud体系,所以Spring Cloud较偏向OpenFeign。
- Feign与RestTemplate的对比。在Spring Cloud体系下,Feign比RestTemplate更适合作为HTTP客户端。
- Feign常见问题解答。对Feign使用中常见的问题如调用404、超时、注解不生效等进行解答。
Feign的使用
- 添加Feign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
- 创建接口并添加
@FeignClient
注解
//定义一个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服务。
-
@FeignClient
中的接口使用Spring MVC注解来声明Web Service请求 - 在主启动类添加
@EnableFeignClients
注解开启Feign的功能
@SpringBootApplication
@EnableFeignClients
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 调用Feign接口的方法即可完成服务调用
@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进行请求转发。它的工作原理是:
- 根据
@FeignClient
的value值找到服务,如果配置了服务注册中心,还会根据服务名获取服务实例列表。 - 构造请求参数,根据方法上的注解(如@PathVariable、@RequestParam、@RequestBody等)确定请求参数。
- 根据注解(@GetMapping、@PostMapping、@DeleteMapping等)确定请求方法和路径。
- 发起请求调用其他服务,获取结果。
- 封装结果,返回给调用方。
Feign的定制化
Feign提供了多种定制化手段:
- 日志级别:Feign支持日志记录,我们可以设置日志级别以查看Feign的调用详情。
feign:
client:
config:
default:
loggerLevel: FULL
设置为ALL,则会展示完整的请求、响应日志详情。
- 编码器和解码器:Feign默认使用JSON进行编码和解码,我们可以设置自定义的编解码器。
@Component
public class CustomEncoder implements Encoder {
//...
}
@Bean
public Encoder feignEncoder() {
return new CustomEncoder();
}
- Feign拦截器:我们可以通过实现
RequestInterceptor
和ResponseInterceptor
接口来定制拦截请求和响应。
@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
//自定义逻辑...
template.header("Content-Type", "application/json");
};
}
- Feign接口的继承:我们的Feign接口可以继承另一个接口,这样我们可以重写父接口的方法来实现特定的定制需求。
@FeignClient("some-service")
public interface SomeServiceClient extends SomeService {
@Override
@GetMapping("/{path}")
String doSomething(@PathVariable("path") String path);
}
- Contract契约:Spring Cloud Feign默认使用SpringMvcContract,我们可以实现自定义契约来控制Feign的一些行为,例如路径、请求方法等。
Feign的运维实践
在实际项目中使用Feign也会遇到一些问题,这里给出一些运维方面的实践建议:
- Feign默认使用JDK原生的URLConnection发送HTTP请求,我们可以选择更高性能的HTTP客户端,如Apache HTTP Client、OkHttp等。
feign:
client:
config:
default:
httpClient:
enabled: true
connectionTimeout: 5000
followRedirects: true
loggerLevel: full
okhttp:
enabled: true # 开启OkHttp
- 超时设置:Feign客户端的超时设置包括:
connectTimeout
:连接超时,默认10秒。readTimeout
:读取超时,默认60秒。
我们可以根据服务调用的耗时情况进行设置,避免超时。
关于feign默认配置参数可以参看FeignClientConfiguration
源码类的字段属性。
- 服务降级:当服务调用失败或超时,我们需要有降级策略,避免影响整体系统。我们可以为Feign接口增加
fallback
指定降级实现类。
@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里的方法作为降级返回。
- 重试:我们可以为Feign增加重试设置,在服务调用失败时进行重试,避免故障扩散。
feign:
client:
retry:
enabled: true # 开启重试
maxAttempts: 5 # 最大重试次数
period: 5000 # 重试间隔时间
参看源码类:feign.Retryer.Default
。
- 负载均衡:Feign默认集成Ribbon进行客户端负载均衡,我们可以设置一些Ribbon相关参数进行配置,如连接超时、重试等。
ribbon:
ConnectTimeout: 1000 # 连接超时时间
ReadTimeout: 3000 # 读取超时时间
OkToRetryOnAllOperations: true # 是否对所有的请求方法都重试
MaxAutoRetriesNextServer: 1 # 切换实例后的最大重试次数
Feign常见问题排查
- Feign调用返回404
这通常是因为Feign调用的url不正确导致的。我们可以:
- 确认
@FeignClient
中的value值正确,对应服务注册中心中的服务名。 - 确认url中的路径正确,可以打印Feign的日志查看实际请求路径。
- 如果服务实例很多,还需要确认Ribbon的负载均衡策略是否导致请求到错误实例。
- Feign调用返回超时
这种情况通常有两种原因:
- 服务提供方处理时间过长,超出Feign的读超时时间。我们需要适当增加Feign的
readTimeout
。 - 服务调用链路过长,中间某个服务处理时间过长,导致Feign总调用时间超出。这种情况下需要检查各个链路的处理时间,进行优化。
- 服务降级没有生效
这种情况主要有两种原因:
@FeignClient
没有指定fallback
属性,没有降级实现类。这种情况下需要添加降级类。- 降级类没有在Spring容器中,没有被扫描到。这种情况需要确认降级类是否被
@Component
注解,或者增加@FeignClient
的fallbackFactory
指定工厂类来生成降级类实例。
- 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日志级别。
- 自定义Client不生效?
- 检查feign.client.name是否设置,且与ClientName一致。
- 检查@FeignClient(“name”)的name属性是否正确,对应feign.client.name。
- 检查Client实现类是否加了@Component注解,是否已正确注入Spring容器。
- 想使用Spring MVC注解,但不生效?
- 确认是否使用的OpenFeign,因为OpenFeign才支持Spring MVC注解。
- 检查OpenFeign的版本是否过低,低版本OpenFeign的注解支持不完整。
- 检查方法与参数上是否都有相应的注解,某个注解缺失会导致不生效。
- 如何给Feign的Bean添加拦截器、AOP等?
- 由于FeignClient通过JDK代理产生,无法直接为其Bean添加拦截器、AOP等。
- 可以通过在FeignClient定义的接口上添加拦截器注解的方式给FeignClient添加拦截器。
- 也可以在FeignClient的实现类上(默认是Feign.Default)添加AOP等。
- 定义自己的Feign拦截器@Component并配置到feign.client.config.defaultInterceptors。
- Feign如何实现文件上传?
- 文件上传需要使用多部分表单,Feign默认的表单编码器FormEncoder不支持。
- 需要添加对multipart/form-data的支持,需要引入feign-form的依赖。
- 并在feign.clients.default.encoders添加MULTIPART_FORM_ENCODER。
- 然后就可以定义接收MultipartFile的Feign接口进行文件上传了。
- Feign可以调用HTTPS接口吗?
可以。Feign默认的Client就支持HTTPS,只需要在@FeignClient的url指定https协议和证书相关配置即可。也可以采用ApacheHttpClient替换默认Client,实现更复杂的HTTPS调用方案。
- 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性能的建议:
- 选择更高性能的HTTP客户端:如前所述,替换Feign默认的URLConnection,选择Apache HTTP Client、OkHttp等更高性能的HTTP客户端。
- 连接池优化:
- 合理设置连接池大小,不宜太大也不宜太小。
- 选择支持连接池复用的HTTP客户端,如OkHttp。
- Ribbon也有连接池设置,与Feign的HTTP客户端配合优化。
- 超时优化:
- 合理设置Feign的连接超时和读取超时。连接超时不宜太长,读取超时根据服务调用耗时设置。
- Ribbon也有相应超时设置,与Feign协同优化。
- 服务线程池优化:
Feign使用JDK默认线程池,我们可以进行定制:
代码语言:javascript复制@Bean
public ExecutorService feignExecutorService(){
// 设置核心线程数,最大线程数,队列大小,释放资源时的延迟时间
return new ThreadPoolExecutor(xx, yy, zz, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(zzz),
new NamedThreadFactory("feign"));
}
- 重试优化:
- 合理设置Feign的重试次数和时间间隔。
- 区分对不同服务的重试策略,防止重试过度导致系统资源消耗过大。
- 日志级别优化:
Feign的日志虽然可以用于调试,但是日志级别过详细也会对性能产生影响,所以可以根据环境设置不同日志级别:
- 开发环境:Logger.Level.FULL,方便开发调试。
- 测试环境:Logger.Level.BASIC,了解大体调用流程。
- 生产环境:Logger.Level.NONE,关闭日志提高性能。
- GZIP压缩:
Feign支持对请求和响应进行GZIP压缩,以提高网络传输性能。我们可以开启:
代码语言:javascript复制feign:
compression:
request:
enabled: true # 开启请求GZIP压缩
response:
enabled: true # 开启响应GZIP压缩
以上这些优化方案可以很好地提高Feign的性能,使其更加适合生产环境。但优化总是有代价的,需要根据系统的吞吐量、调用链路等实际情况进行权衡和调优。
Feign 性能测试
对Feign客户端进行、性能和可靠性测试也很重要,这里给出一些测试建议:
- 单元测试:
我们可以为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);
}
}
- 集成测试:
启动全部服务,然后调用Feign客户端进行端到端测试,校验整体流程和结果正确性。
- 压力测试:
使用工具如JMeter对Feign客户端进行大并发调用,测试Feign在高负载下的性能表现是否达标。主要关注:
- QPS:每秒查询率,需要达到系统预期的QPS。
- RT:响应时间,需要控制在可接受范围内,不宜太长。
- 错误率:服务调用错误率需要控制在可接受范围内。
- 资源消耗:CPU、内存、网络bandwidth等资源消耗是否在可控范围。
- 稳定性测试:
- 长时间高负载测试:模拟高访问量场景,长时间大并发访问Feign客户端,测试其稳定性。
- 服务故障测试:模拟服务提供方部分实例故障,测试Feign的容灾性和降级是否生效。
- 网络故障测试:模拟网络抖动、高延迟、短时网络中断等情况,测试Feign的稳定性。
这些测试可以有效检验Feign客户端的健壮性,保证其在复杂环境下仍能稳定运行。我们还可以根据系统的重要程度和流量规模,制定不同的性能指标和可用性指标,对Feign进行严苛的SLA考核。
套用一些流行框架如:
- Spring Cloud Contract用于微服务contract测试
- Resilience4j进行熔断、限流、重试等过载防护
- Hystrix进行熔断和线程隔离
这可以更好保证Feign的高可用。综上,测试和过载防护是保证Feign稳定性的重要一环,希望通过这些测试实践和框架应用,可以让Feign在复杂环境下表现更加可靠。
Feign高可用方案
对于一个微服务系统来说,服务调用是非常重要的一个环节,Feign作为一个重要的调用组件,其高可用性直接影响整个系统的高可用。这里给出一些提高Feign高可用的方案:
- 服务发现与注册:
Feign常与Eureka、Consul等服务注册中心协同使用,这些服务注册中心本身也支持集群部署,可以提高Feign服务调用的高可用性。
- Ribbon负载均衡:
Feign内置的Ribbon组件,我们可以设置多个服务实例,并选择合适的负载均衡策略,避免单点故障。
- Hystrix容错保护:
Hystrix可以进行线程隔离、熔断等策略保护Feign,避免在高并发下服务被过载。熔断机制可以快速失败,避免操作阻塞。
- Http客户端连接池:
使用连接池,如Apache HTTP Client、OKHttp等,可以进行连接复用,避免每次调用都建立新的连接。并且这些客户端本身也支持高可用配置,如设置多个Url地址。
- 超时与重试机制:
合理设置Feign的连接超时、读取超时时间,可以快速发现服务问题并快速失败,避免资源占用过长时间。配合重试机制,在一定次数后快速返回,防止长时间的不可用服务导致系统不可用。
- 熔断与限流:
除Hystrix外,可以使用Resilience4J等开源组件进行更加全面和强大的熔断、限流、重试。限流可以防止突发高流量导致系统不可用。
- 服务跟踪:
使用组件如Zipkin进行服务调用链路跟踪,一旦出现高延迟或不可用服务,可以快速定位问题所在。
- 降级策略:
为Feign接口指定Fallback以及合适的降级策略,在服务不可用时提供备选方案,避免不可用服务导致依赖服务完全不可用。
- 日志记录与监控:
合理配置Feign日志级别,并结合ELK等日志收集工具进行监控。一旦服务有异常状况,可以快速发现并定位问题。
综上,Feign高可用需要多方面的保障和运维,需要与服务注册中心、熔断限流组件、链路跟踪组件、监控日志组件等协同配合,共同提高Feign和依赖其的整个微服务系统的高可用性。
Feign源码分析
理解Feign的源码,有助于我们更深入理解其工作原理,从而合理使用和定制Feign。这里简要分析Feign的源码:
- Feign类:Feign类是Feign的入口,主要工作是:
- 解析Feign注解,获取接口方法与url映射关系,请求类型等信息。
- 构建ReflectiveFeign类,封装接口方法与请求细节的映射。
- 构建Feign.Builder,用于创建Feign实例。
- 创建Logger用于记录Feign日志。
- 绑定Contract契约,默认是SpringMvcContract。
- ReflectiveFeign类:
- 维护Feign接口方法与请求模板(RequestTemplate)的映射。
- 调用接口方法时,查找请求模板,使用请求参数构造URL,发起HTTP请求。
- 将响应结果转换为接口方法 defined 返回类型,返回给调用方。
- Contract接口与SpringMvcContract:
- Contract接口定义了诸如生成请求模板、构造参数值到模板变量等规则。
- SpringMvcContract实现了Spring MVC注解方式,将方法、参数注解转化为请求模板变量与值。
- Client接口与Client.Default实现:
- Client接口定义了发起HTTP请求的方法。Feign使用构建器模式,允许我们选择不同Client实现来发送请求。
- Client.Default实现了使用JDK原生URLConnection发送HTTP请求。我们可以实现自定义Client,如使用OKHttp等。
- Encoder和Decoder:
- Encoder负责对请求参数进行编码,默认使用SpringEncoder对参数进行JSON编码。
- Decoder负责对响应结果进行解码,默认使用SpringDecoder对JSON响应进行解码。
- Logger和LoggingInterceptor:
- Logger定义了记录Feign日志的规范,有4个级别:NONE、BASIC、HEADERS、FULL。
- LoggingInterceptor拦截Feign请求与响应,将详细信息记录为Feign日志, logfile可指定日志记录位置。
- Retryer接口:定义重试策略,Feign内置支持backoff、exponential backoff重试策略。我们也可以自定义Retryer实现。
这些是Feign的主要组成部分, Feign的高效与灵活正是因为这些组件采用接口设计,允许我们灵活选择与替换。理解这些组件的作用与关系,有助于我们使用Feign的源码进行定制化开发。
Feign与OpenFeign区别
Feign和OpenFeign都是Netflix开源的声明式HTTP客户端,但有一定的区别:
- 源起:
Feign起源于Netflix,后捐给Spring Cloud并成为Spring Cloud Netflix的一部分。OpenFeign是Spring Cloud对Feign进行增强,成为Spring Cloud的组成部分,目的是提供Spring MVC注解的支持、 wrongly监听机制等新功能。
- 注解支持:
Feign仅支持JAX-RS注解,对Spring MVC注解不支持。OpenFeign支持Spring MVC注解,更贴近Spring开发体验,支持内容协商、验证等机制。
- 编解码器:
Feign仅支持QueryStringEncoder、FormEncoder和JsonEncoder三种编解码器。OpenFeign内置了SpringEncoder和StringDecoder,支持更丰富的对象与HTTP请求的编解码,如集合、 maps等。
- contract:
Feign仅支持接口方法签名与url的映射,请求细节无法定制。OpenFeign支持SpringMvcContract,可以定制请求方法、参数绑定等细节。
- 拦截器:
Feign不支持请求与响应拦截器。OpenFeign支持ClientRequestInterceptor和ClientResponseInterceptor,允许拦截并自定义Feign的请求与响应。
- 监听支持:
Feign不支持对指标与事件的监听。OpenFeign支持监听连接池大小、请求计数、处理时间等指标,以及连接成功、失败等事件。方便监控Feign运行状态。
- 对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客户端,但有以下主要区别:
- 使用方式:
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);
}
- 可维护性:
Feign的接口更加抽象,屏蔽了实现细节,有利于后续维护和替换。RestTemplate的调用方式过于具体,不利于变更。
- 可扩展性:
Feign有更加丰富的扩展点,如支持多种编解码器、拦截器、Client等。RestTemplate扩展比较困难。
- 支持功能:
Feign内置了负载均衡、重试、监听等机制。而RestTemplate需要与其他组件配合使用才能完成,如Ribbon等。
- 与Spring的整合:
Feign是Spring Cloud的一部分,与Spring框架深度整合。而RestTemplate需要自己进行与Spring的整合。
Feign与其它组件的关系、区别
- Feign与Ribbon的区别和关系:
- Feign和Ribbon都是Netflix开源的组件,用于微服务调用。
- Feign是一个声明式的HTTP客户端,主要负责HTTP请求的发送。
- Ribbon是一个负载均衡器,主要用于客户端的负载均衡。
- Feign内部使用Ribbon进行负载均衡,所以当使用Feign时,不需要再单独使用Ribbon。
- 但我们仍然可以在Feign中配置或替换Ribbon,实现自定义的负载均衡策略。
- Feign与Hystrix的区别和关系:
- Feign和Hystrix也都是Netflix开源的组件,用于微服务体系。
- Feign是一个HTTP客户端,主要用来发送HTTP请求。
- Hystrix是一个容错管理工具,主要用来提高微服务的可靠性与容错能力。
- Feign可以与Hystrix结合,使用Hystrix的容错机制来保护Feign的服务调用。
- 我们只需要在Feign客户端上添加@FeignClient中的fallback指定容错方法即可启用Hystrix容错。
- 也可以手动在fallback方法中使用HystrixCommand进行服务降级等操作。
- Feign与Zuul的区别和关系:
- Feign是Netflix的HTTP客户端,用于服务调用。Zuul是Netflix的网关,用于路由转发。
- 一个微服务体系通常会同时使用Feign和Zuul这两个组件。
- Feign用于服务内部的调用,Zuul主要用于外部访问系统的统一入口。
- Zuul可以与Feign结合,将外部访问路由到内部服务,而这些内部服务之间可以使用Feign相互调用。
- 所以Zuul和Feign虽然功能不同,但可以良好配合,共同支撑起一个微服务架构。
这些内容的理解可以让我们对Feign与其他微服务组件有一个更全面的认知,知晓它们之间的关系与区别,这有助于我们在设计微服务方案时做出更好的选择与组合。Feign虽然功能十分强大,但它通常并不孤立存在,而是与其他组件配合使用,发挥更大的价值。
Feign的高级内容、自定义扩展实践
- 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()
两个方法。
- Feign的请求模板:
有时候我们的Feign方法中存在大量重复的注解与参数,这时可以使用Feign的请求模板进行抽取。如:
代码语言:javascript复制@Configuration
public class FeignConfig {
@Bean
public RequestInterceptor basicAuthRequestInterceptor() {
return template -> {
template.header("Authorization", "Basic YWRtaW46YWRtaW4=");
template.query("limit", 100);
};
}
}
然后在@FeignClient
中指定这个拦截器:
@FeignClient(name = "example", configuration = FeignConfig.class)
public interface ExampleFeignClient {
//这个方法将自动继承basicAuthRequestInterceptor中的模板参数
@GetMapping("/users")
List<User> getUsers();
}
这样getUsers()
方法就不需要再添加limit
参数和Authorization
header了。
- 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虽然简单易用,但其实它的机制相当灵活丰富,满足我们的定制需求。
- Feign的拦截器:
Feign支持使用拦截器对其请求进行拦截,我们可以实现以下拦截器:
- RequestInterceptor:在请求发出之前拦截,可以修改请求。
- ResponseInterceptor:在得到响应之后拦截,可以修改响应。
- ErrorInterceptor:在出现异常时拦截,可以修改或重试请求。
可以通过 feign.client.config.defaultInterceptors
添加拦截器,如:
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的行为,实现复杂的请求处理逻辑。
- Feign的解码器:
Feign默认使用ResponseEntityDecoder
对响应进行解码,将其解码为ResponseEntity<T>
对象。我们可以实现自己的解码器,如:
public class ExampleDecoder implements Decoder {
@Override
public Object decode(Response response, Type type) {
if (type == Example.class) {
//对响应进行定制化解码
return ... ;
}
}
}
然后通过feign.client.config.decoder
指定这个解码器,如:
feign:
client:
config:
example: #指定FeignClient名称
decoder: com.example.ExampleDecoder
定制的解码器让我们可以对Feign的响应进行转换和定制,以适配服务的响应格式。
- Feign的校验器:
Feign也支持Validator校验器,当接收到的响应无法正确解码时,可以使用校验器进行二次校验和处理。我们需要实现Validator
接口,并指定给FeignClient使用。
Feign的拦截器、解码器和校验器让我们可以高度定制Feign的请求/响应处理过程,这也是Feign高扩展性的体现。利用这些机制,我们可以轻易地让Feign支持定制的协议格式、复杂的请求/响应流程。这使得Feign不仅仅是一个简单的HTTP客户端,还可以成为一个功能丰富的通用服务调用方案。
- 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的优化是一个比较重要的话题。
- Feign的可选参数:
Feign默认采用JAXRS注解将方法映射到HTTP请求,但JAXRS注解不支持可选参数。为支持可选参数,我们可以:
- 给可选参数定义默认值:
@GetMapping("/users")
List<User> getUsers(@RequestParam(value="age", required=false) Integer age);
- 使用
@Nullable
注解标记可选参数:
@GetMapping("/users")
List<User> getUsers(@Nullable @RequestParam Integer age);
- 在方法中追加一个
java.util.Optional<T>
类型的参数:
@GetMapping("/users")
List<User> getUsers(Optional<@RequestParam Integer> age);
- 采用Builder模式构建参数,可选参数不传入Builder:
@GetMapping("/users")
List<User> getUsers(UserSearchCriteria criteria);
UserSearchCriteria criteria = UserSearchCriteria.builder()
.age(30)
.build();
这些方式可以让我们的Feign接口支持可选参数,变得更加灵活。
- Feign的文件上传:
Feign默认不直接支持文件上传,我们有以下方式实现:
- 使用
feign.codec.Encodedecode.MultiPartFormContent
编码器:
@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的底层客户端进行文件上传:
@PostMapping("/upload")
void uploadFile();
public void uploadFile() {
Client client = ...; //获取Feign的Client
Request request = ... //构建文件上传请求
client.execute(request);
}
- 采用Feign的拦截器获取文件,然后手动构建MultiPart请求:
@PostMapping("/upload")
void uploadFile();
public void uploadFile() {
File file = ...; //获取要上传的文件
String uploadUrl = ...; //获取上传URL
//手动构建MultiPart请求
MultiPartRequest request = new MultiPartRequest(uploadUrl);
request.addFile("file", file);
... ...
}
这些方式可以让我们的Feign接口支持文件上传的功能,对接那些需要文件上传的服务。
Feign虽然简单易用,但我们也可以通过各种方式进行扩展,实现较为复杂的服务调用需求。
- Feign的OAuth2支持:
Feign默认不支持OAuth2,我们可以通过以下方式实现:
- 自定义RequestInterceptor拦截器,在每个请求加入OAuth2的Authorization头。
- 扩展Feign的Contract,把OAuth2的Authorization头数据注入到每个请求模板中。
- 利用Zuul的OAuth2支持,在网关处获得访问令牌,然后把令牌转发给Feign客户端。
- 直接使用Feign的底层客户端,在执行每个请求前,从OAuth2服务器获取访问令牌,并手动加入到请求头中。
Feign的Hystrix支持
Feign默认提供了对Hystrix的集成支持,我们可以很容易地为Feign接口启用Hystrix。主要有以下方式:
- 在application.yml中全局指定:
feign:
hystrix:
enabled: true
- 在@FeignClient中指定fallback:
@FeignClient(name = "user", fallback = UserClientFallback.class)
public interface UserClient {
}
- 实现FallbackFactory:
@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属性:
feign:
client:
config:
user: #FeignClient名称
hystrix:
command: #HystrixCommand配置
default:
execution.isolation.thread.timeoutInMilliseconds: 1000
- 定制Hystrix的线程池、信号量隔离策略:
@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等,实现多样化的服务集成。 我们可以通过两种方式扩展:
- 全局配置: 覆盖feign.codec.decoder和feign.codec.encoder属性。
- 局部配置: 为某个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可以通过以下方式添加请求头信息:
- 方法上的@Header注解:
@FeignClient("serviceName")
public interface Client {
@RequestMapping(method = RequestMethod.GET, "/hello")
@Header("token: 1234") //添加头信息
String hello();
}
- 为FeignClient添加defaultHeaders属性:
@FeignClient(name = "clientName", defaultHeaders = {
"token: 1234"
})
public interface Client {
//...
}
- 配置feign.client.headers:
feign:
client:
headers:
token: 1234
- 为方法添加HeaderInterceptor:
@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的使用技巧以及优化经验,相信能帮助大家在实际生产项目中正确使用。