Skip to content

Feign 服务调用

作者:唐亚峰 | battcn
字数统计:1.4k 字

使用微服务必不可少会用到 Feign。 为了方便大家无感知使用请认真熟读本章节

Spring Cloud OpenFeign 是Spring 官方为替代进入停更维护状态的 Feign 推出的另一款声明式服务调用与负载均衡组件。

它具有 Feign 的所有功能,并在其基础上增加了对 Spring MVC 注解的支持。

基础知识

添加下面依赖即可快速使用 Spring Cloud OpenFeign 与负载均衡

xml
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

负载均衡

当需要用 RestTemplate 显示调用的时,需要给 RestTemplate 的实例上添加 @LoadBalanced 表示它是一个负载均衡的调用

java
@Bean
@Primary
@LoadBalanced
public RestTemplate lbRestTemplate() {
    return new RestTemplate();
}

性能优化

Gzip 压缩

服务之间调用或者文件传输的时候开启压缩支持可以大大的提升响应效率

开启压缩可以有效节约网络资源,但是会增加CPU压力,建议把最小压缩的文档大小适度调大一点。

yaml
feign:
  compression:
    request:
      # 开启请求压缩
      enabled: true
      # 配置压缩支持的 MIME TYPE
      mime-types: text/xml,application/xml,application/json 
      # 配置压缩数据大小的下限
      min-request-size: 2048   
    response:
      # 开启响应压缩
      enabled: true

连接池配置

开启 http-client 配置替换默认的 URLConnection 同时配置连接池和超时时间,提高响应速度

yaml
feign:
  httpclient:
    enabled: true # HttpClient的开关
    connection-timeout: 3000       # 连接超时(毫秒)
    time-to-live: 3                # 连接存活时长
    time-to-live-unit: minutes     # 连接存活时长单位
    max-connections: 500  # 最大连接数
    max-connections-per-route: 50   # 单个请求路径的最大连接数

详细日志

Bean 注入的方式

配置如下 @Bean 既可开启详细日志(不过在生产环境建议关掉,可以通过表达式控制 @Bean 在生产环境不注入)

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

配置文件方式

yaml
feign:
  client:
    config:
      default:  #这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
        loggerLevel: FULL #日志级别

进阶知识

扩展封装

为了方便开发过程中减少繁琐的代码冗余问题,专门针对 Feign 请求做了下列功能的增强

  • 数据解码
  • 代理请求
  • 请求透传
  • 自动刷新 TOKEN
xml
<dependency>
    <groupId>com.wemirr.framework</groupId>
    <artifactId>feign-plugin-spring-boot-starter</artifactId>
</dependency>

数据解码

这是一个很实用的功能,很多时候我们希望写 @FeignClient 的时候规避掉 Result.class、R.class 统一类型,只想拿结果集直接处理

// 配置全局解码之前 
@GetMapping("/list")
Result<List<POJO>> list();

// 配置全局解码之后
@GetMapping("/list")
List<POJO> list();

上面的案例中,如果返回的统一对象还得先提取一下 List<POJO> list = result.getData() 还得判断空以免出现 NPE 的问题。

写多了同时会影响代码整洁,那么配置全局解码后即可忽略全局对象,同时默认采用 SpringDecoder 而非 FeignDefaultDecoder 更加贴合日常开发

注意

配置之后并不影响你写 Result<List<POJO>> 这样的代码,所以你无需担心,因为它是兼容的

java
@Bean
public Decoder feignDecoder(ObjectFactory<HttpMessageConverters> messageConverters,
                            ObjectProvider<HttpMessageConverterCustomizer> customizers) {
    return new OptionalDecoder((new ResponseEntityDecoder(new FeignResponseDecoder(new SpringDecoder(messageConverters, customizers)))));
}


@Bean
public ErrorDecoder errorDecoder() {
    return (s, response) -> {
        log.warn("response status is:{}", response.status());
        return new ErrorDecoder.Default().decode(s, response);
    };
}

请求透传

这个还是很有用的,不然 FeignClient 在发送 HTTP 请求的时候默认是不会携带上游 Header 的,意味着你后续接口如果有 Token 限制就会访问受限

java
@Bean
@Order(-999999)
public FeignPluginInterceptor feignPluginInterceptor(FeignPluginProperties properties) {
    return new FeignPluginInterceptor(properties);
}

代理请求

一个很实用的功能 它可以使你在微服务场景下向单机一样的去写代码,在也不用苦恼开发阶段需要开一堆服务机器内存不够了

可以让你在内网开发过程中,只需要启动编码的程序即可,其它服务会自动代理请求配置的 HTTP 地址去

举个例子:假设你跟很多小伙伴开发订单服务,它需要依赖商品认证会员等服务的接口,而你又不能将订单服务注册到测试环境中去,这时候自己启动一堆服务机器又卡,开发效率也低下,那么就可以用这种方式啦

配置启用

yaml
extend:
  feign:
    plugin:
      mock:
        enabled: true
        server-map:
          baseserver:
            # 真实的请求地址
            server-url: http://192.168.1.1:5002/base/api

核心代码

javascript
@Bean
@Primary
@ConditionalOnProperty(prefix = MockProperties.MOCK_PREFIX, name = "enabled", havingValue = "true")
public Client feignClient(LoadBalancerClient loadBalancerClient, LoadBalancerClientFactory loadBalancerClientFactory,
                          List<LoadBalancerFeignRequestTransformer> transformers,
                          MockProperties mockProperties) {
    return new MockLoadBalancerFeignClient(new Client.Default(null, null),
            loadBalancerClient, loadBalancerClientFactory, transformers, mockProperties);
}

自动刷新 Token

解决内部服务调用时没有 Token 的尴尬情况

比如定时任务调用访问受限的接口,这时候没有 Token 就自动根据配置的系统账号生成一个

配置启用

yaml
extend:
  feign:
    plugin:
      token:
        enabled: true
        # 是否为负载均衡请求(最终还是被我改成真实请求了)
        load-balance: true
        # Feign 服务地址
        uri: http://wemirr-platform-authority/oauth2/token
        # 如果定时任务需要动态设置租户目前没想到好的方案,欢迎提供思路和建议
        o-auth:
          client-id: messaging-client
          client-secret: 123456
          username: admin
          password: 123456
          tenant-code: "0000"
          scope: platform

核心代码

基于 FeignInterceptor 做了拦截处理,在请求之前模拟登陆将 Token 存在本地缓存中,提高性能

java
@Bean
@ConditionalOnProperty(prefix = AutoRefreshTokenProperties.TOKEN_PREFIX, name = "enabled", havingValue = "true")
public AutoRefreshTokenInterceptor feignTokenInterceptor(AutoRefreshTokenProperties properties) {
    final AutoRefreshTokenProperties.Cache cache = properties.getCache();
    Cache<String, String> tokenCache = CacheBuilder.newBuilder().initialCapacity(cache.getInitialCapacity())
            .maximumSize(cache.getMaximumSize()).expireAfterWrite(cache.getExpire(), TimeUnit.SECONDS).build();
    return new AutoRefreshTokenInterceptor(properties, tokenCache);
}