Spring Cloud Feign(声明式服务调用)使用指南

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
日志服务 SLS,月写入数据量 50GB 1个月
应用型负载均衡 ALB,每月750个小时 15LCU
简介: Spring Cloud Feign(声明式服务调用)使用指南

简介

Feign 可以把 Rest 的请求进行隐藏,伪装成类似 Spring MVC 的 Controller 一样。不用再自己拼接 url,拼接参数等等操作,一切都交给Feign去做。


入门案例

  1. 在服务消费者导入依赖

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
  2. 在启动类上添加@EnableFeignClients注解

    Feign中已经自动集成了Ribbon负载均衡,因此不需要自己定义RestTemplate

    @SpringCloudApplication
    @EnableFeignClients        // 开启Feign注解
    public class ConsumerApplication {
        public static void main(String[] args) {
            SpringApplication.run(ConsumerApplication.class, args);
        }
    }
  3. 编写Feign客户端

    @FeignClient(value = "user-service")    // 添加FeignClient,指定服务ID
    public interface UserClient {
        /**
         * 声明一个feign的接口,它的实现是服务提供者的controller实现
         */
        @GetMapping("/user/{id}")
        User getById(@PathVariable("id") Long id);
    }
  4. 代码中调用,使用userClient访问:

    @Autowired  // 注入UserClient
    private UserClient userClient;
    
    public User getUserById(@PathVariable long id) {
        User user = userClient.getById(id);
        return user;
    }


@FeignClient 注解说明

@FeignClient:声明这是一个 Feign 客户端,同时通过 name / value 属性指定服务名称

  • 只能标注在远程服务调用的接口上

    (FeignClient注解被 @Target(ElementType.TYPE) 修饰,表示 FeignClient 注解的作用目标在接口上)

  • 当 SpringCloud 扫描到被 @FeignClient 标识的接口时,底层会为其创建实现类代理对象(jdk代理),并交给 spring容器管理(注册IOC容器);创建出来 Bean 对象的名字由注解的name或value属性指定,若对同一个服务创建两个远端服务调用接口时会报错
  • 接口中定义的方法,完全采用 SpringMVC 的注解,Feign 会根据注解生成 URL,并访问获取结果
  • 服务的启动类或配置类需要标注 @EnableFeignClients 注解才能使 Fegin 生效


@FeignClient注解的常用属性如下:

  • name / value:指定 FeignClient 的名称,如果项目使用了Ribbon(注册中心),name属性会作为微服务的名称,用于服务发现
  • url:一般用于调试,可以手动指定@FeignClient调用的地址。默认为空

    url 可以从配置文件获取,如果有则通过 url 调用,没有则根据服务名调用。

    格式为 url = "${xxx.xxx.xxx: }"

  • configuration:Feign配置类,可以自定义 Feign 的 Encoder、Decoder、LogLevel、Contract,可以为每一个 FeignClient 指定不同的配置
  • fallback:定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
  • fallbackFactory:工厂类,用于生成 fallback 类示例,通过这个属性可以实现每个接口通用的容错逻辑,减少重复的代码
  • path:定义当前 FeignClient 的统一前缀,当项目中配置了 server.context-path,server.servlet-path 时使用
  • decode404:当发生 http 404 错误时,如果该字段位 true,会调用 decoder 进行解码,否则抛出 FeignException


调用方式:

  • 方式一:接口提供方在注册中心

    如果服务提供方已经注册到注册中心了,那么name或者value的值为:服务提供方的服务名称。必须为所有客户端指定一个name或者value

    @FeignClient(value="run-product",fallback = ProductClientServiceFallBack.class)
  • 方式二:单独的一个http接口,接口提供方没有注册到注册中心

    @FeignClient(name="runClient11111",url="localhost:8001")

    此处name的值为:调用客户端的名称

以上两种方式都能正常调用。name可以为注册中心的服务名称,加上url属性时,name的值就与注册中心服务名称无关。


Feign Client 的配置

feign 配置是在 ribbon 配置的基础上做了扩展,可以支持服务级超时时间配置,所以,feign 配置和 ribbon 配置的效果是⼀样的。

SpringCloud 对配置的优先级顺序如下:

  • Feign局部配置 > Feign全局配置 > Ribbon局部配置 > Ribbon全局配置
  • 配置文件属性和配置类的优先级顺序为:配置文件属性配置 > 配置类代码配置
feign:
  client:
    config:
      default:    # 全部服务配置
        connectTimeout: 5000    # 建立连接的超时时长,单位:毫秒。默认为1000
        readTimeout: 5000        # 指建立连接后从服务端读取到可用资源所用的超时时间,单位:毫秒。默认为1000
        loggerLevel: FULL        # 日志级别
        errorDecoder: com.example.SimpleErrorDecoder  # Feign的错误解码器,相当于代码配置方式中的ErrorDecoder
        retryer: com.example.SimpleRetryer  # 配置重试,相当于代码配置方式中的Retryer
        requestInterceptors: # 配置拦截器,相当于代码配置方式中的RequestInterceptor
          - com.example.FooRequestInterceptor
          - com.example.BarRequestInterceptor
        decode404: false    # 是否对404错误解码
        encode: com.example.SimpleEncoder
        decoder: com.example.SimpleDecoder
        contract: com.example.SimpleContract
      serverName:    # 单独给某⼀服务配置。serverName是服务名,使⽤的时候要⽤服务名替换掉这个
        connectTimeout: 5000
        readTimeout: 5000


Feign 请求添加 headers

方案一:方法上的 @RequestMapping 注解添加 headers 信息

@RequestMapping 注解的属性中包含一个 headers 数组,可以在指定的方法上 @RequestMapping 注解中添加需要的 headers,可以是写死的,也可以读取配置=

同理 @RequestMapping 一组的 @PostMapping,@GetMapping 注解等均适用

@FeignClient(name = "server",url = "127.0.0.1:8080")
public interface FeignTest {
    @RequestMapping(value = "/test",headers = {"app=test-app","token=${test-app.token}"})
    String test();
}


方案二:接口上的@RequestMapping注解添加headers信息

如果同一个接口中所有的方法都需要同样的headers,可以在接口上的@RequestMapping注解中添加headers,使整个接口的方法均被添加同样的headers

@FeignClient(name = "server",url = "127.0.0.1:8080")
@RequestMapping(value = "/",headers = {"app=test-app","token=${test-app.token}"})
public interface FeignTest {
    @RequestMapping(value = "/test")
    String test();
}


方案三:使用@Headers注解添加headers信息(不推荐)

@FeignClient(name = "server",url = "127.0.0.1:8080")
@Headers({"app: test-app","token: ${test-app.token}"})
public interface FeignTest {
    @RequestMapping(value = "/test")
    String test();
}

查看openfeign官方文档发现其使用的是@Headers来添加headers,测试发现并没有生效,spring cloud使用了自己的SpringMvcContract来解析注解,所以需要自己实现一个Contract来实现对@Headers注解的支持,具体实现参照(https://juejin.im/post/6844903961653149709


方案四:自定义RequestInterceptor添加headers信息

feign提供了一个拦截器接口RequestInterceptor,实现RequestInterceptor接口就可以实现对feign请求的拦截,接口提供了一个方法apply(),实现apply()方法

实现apply()方法直接添加header会拦截所有的请求都加上headers,如果不是所有的feign请求都需要用到不建议此方法

@Component
public class FeignRequestInterceptor implements RequestInterceptor {

    @Value("${test-app.token}")
    private String token;
    
    @Override
    public void apply(RequestTemplate requestTemplate) {
        requestTemplate.header("app","test-app");//静态
        requestTemplate.header("token",token);//读配置
    }
}


方案五:自定义RequestInterceptor实现添加动态数据到header

以上方案都不适合把动态的数据放入headers中,而通常场景下可能经常需要把计算的签名,用户id等动态信息设置到headers,所以还需要一个更加完善的方案。方案1/2/3均不能设置动态的值,方案4可以设置动态值,但是没做到请求的区分,所以在方案4的基础上进行改进得到了较为完善的方案5。具体实现如下:

在请求调用代码中,获取到HttpServletRequest对象,将需要添加到headers中的值封装成一个map后放入HttpServletRequest的attribute域中

    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();
    String signedMsg = getSignedMsg(reqJson);         // 计算签名字符串
    Map<String, String> reqMap = new HashMap<>();
    reqMap.put("content-type", "application/json");    //常量字段
    reqMap.put("accessKey", accessKey);        //常量字段
    reqMap.put("signedMsg", signedMsg);        //动态计算/获取字段
    request.setAttribute("customizedRequestHeader", reqMap);

在自定义RequestInterceptor中获取到HttpServletRequest对象的attribute域中指定的key,将key对应map中的所有参数加入到headers。

@Component
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 设置自定义header
        // 设置request中的attribute到header以便转发到Feign调用的服务
        Enumeration<String> reqAttrbuteNames = request.getAttributeNames();
        if (reqAttrbuteNames != null) {
            while (reqAttrbuteNames.hasMoreElements()) {
                String attrName = reqAttrbuteNames.nextElement();
                if (!"customizedRequestHeader".equalsIgnoreCase(attrName)) {
                    continue;
                }
                Map<String,String> requestHeaderMap = (Map)request.getAttribute(attrName);
                for (Map.Entry<String, String> entry : requestHeaderMap.entrySet()) {
                    requestTemplate.header(entry.getKey(), entry.getValue());
                }
                break;
            }
        }
    }
}


负载均衡 (Ribbon)

Feign中本身已经集成了Ribbon依赖和自动配置,默认支持Ribbon。

Fegin内置的ribbon默认设置了请求超时时长,默认是1000ms。因为Ribbon内部有重试机制,一旦超时,会自动重新发起请求

可以通过配置来修改:

全局配置 使用 ribbon.=
ribbon:
  ReadTimeout: 2500     # 数据通信超时时长,单位:ms。默认为1000
  ConnectTimeout: 500     # 连接超时时长,单位:ms。默认为1000
  OkToRetryOnAllOperations: false     # 是否对所有的异常请求(连接异常和请求异常)都重试。默认为false
  MaxAutoRetriesNextServer: 1         # 最多重试多少次连接服务(实例)。默认为1。不包括首次调用
  MaxAutoRetries: 0     # 服务的单个实例的重试次数。默认为0。不包括首次调用
  NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule    # 切换负载均衡策略为随机。默认为轮询策略
指定服务配置 <服务名称>.ribbon. =
serverName:    # 单独给某⼀服务配置。serverName是服务名,使⽤的时候要⽤服务名替换掉这个
  ribbon:
    connectTimeout: 5000
    readTimeout: 5000


容错机制

Hystrix支持

Feign默认也有对Hystix的集成,只不过,默认情况下是关闭的。需要通过下面的参数来开启:

feign:
  hystrix:
    enabled: true    # 开启hystrix熔断机制
     
hystrix:
  command:
    default:    # 全局默认配置
      execution:    # 线程隔离相关
        timeout:
          enabled: true        # 是否给方法执行设置超时时间,默认为true。一般不改。
        isolation:
          strategy: THREAD    # 配置请求隔离的方式,这里是默认的线程池方式。还有一种信号量的方式semaphore,
          thread:
            timeoutlnMilliseconds: 10000    # 方式执行的超时时间,默认为1000毫秒,在实际场景中需要根据情况设置
      circuitBreaker:    # 服务熔断相关
        requestVolumeThreshold: 10            # 触发熔断的最小请求次数,默认20
        sleepWindowInMilliseconds: 10000    # 休眠时长,单位毫秒,默认是5000毫秒
        errorThresholdPercentage: 50        # 触发熔断的失败请求最小占比,默认50%
    serverName:    # 单独给某⼀服务配置
      execution:
        timeout:
          enabled: true
        isolation:
          strategy: THREAD
          thread:
            timeoutlnMilliseconds: 10000

注意:

  • Hystix的超时时间应该比Ribbon重试的总时间要大 ,否则Hystrix命令超时后,该命令直接熔断,重试机制就没有任何意义了。

    Ribbon:总重试次数 = 访问的服务器数 * 单台服务器最大重试次数

    即 总重试次数 = (1+MaxAutoRetriesNextServer)*(1+MaxAutoRetries )

    Hystrix超时时间 > (Ribbon超时时间总和)* 重试次数

    故 建议hystrix的超时时间为:

    ( ( 1+MaxAutoRetriesNextServer) * (1+MaxAutoRetries ) ) * (ReadTimeout + connectTimeout)

    • MaxAutoRetries:Ribbon配置: 服务的单个实例的重试次数。不包括首次调用
    • MaxAutoRetriesNextServer:Ribbon配置: 最多重试多少次连接服务(实例)。不包括首次调用
    • ReadTimeout:Ribbon配置: 通信超时时间
    • connectTimeout:Ribbon配置: 建立连接超时时间


Sentinel支持

Sentinel 可以通过并发线程数模式的流量控制来提供信号量隔离的功能。并且结合基于响应时间的熔断降级模式,可以在不稳定资源的平均响应时间比较高的时候自动降级,防止过多的慢调用占满并发数,影响整个系统。

依赖

        <!--Sentinel-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

在配置文件中开启Feign对Sentinel的支持

feign:
  sentinel:
    enabled: true


Feign开启容错机制支持后的使用方式

Feign 开启 Hystrix 或 Sentinel 容错机制支持后的使用方式均是如下两种:

  • 方案一:直接继承被容错的接口,并为每个方法实现容错方案
  • 方案二:实现FallbackFactory接口


方案一:直接继承被容错的接口,并为每个方法实现容错方案
  1. 定义一个类,作为fallback的处理类 。直接继承被容错的接口,并为每个方法实现容错方案

    @Component
    public class UserClientFallback implements UserClient {
        @Override
        public User getById(Long id) {
            return new User(1L, "我是备份-feign", 18, new Date());
        }
    }
  2. 在 @FeignClient 注解中使用 fallback 属性指定自定义的容错处理类

    @FeignClient(value = "user-service",fallback = UserClientFallback.class)
    public interface UserClient {
        @GetMapping("/user/{id}")
        User getById(@PathVariable("id") Long id);
    }
  3. 测试验证

    image-20200916144915246.png


方案二:实现FallbackFactory接口。可以拿到具体的服务错误信息,便于后期排查问题
@FeignClient(value="34-SPRINGCLOUD-SERVICE-GOODS", fallbackFactory = GoodsRemoteClientFallBackFactory.class)
public interface GoodsRemoteClient {
    
    @RequestMapping("/service/goods")
    public ResultObject goods();
}
@Component
public class GoodsRemoteClientFallBackFactory implements FallbackFactory<GoodsRemoteClient> {

    @Override
    public GoodsRemoteClient create(Throwable throwable) {
        return new GoodsRemoteClient() {
            @Override
            public ResultObject goods() {
                String message = throwable.getMessage();    // message即为错误信息
                System.out.println("feign远程调用异常:" + message);
                return new ResultObject();
            }
        };
    }
}


请求压缩(feign.compression)

Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数即可开启请求与响应的压缩功能:

feign:
  compression:
    request:
      enabled: true
    response:
      enabled: true

也可以对请求的数据类型,以及触发压缩的大小下限进行设置,只有超过这个大小的请求才会对其进行压缩:

feign:
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048


日志级别

通过logging.level.xx=debug来设置日志级别。然而这个对Fegin客户端而言不会产生效果。因为@FeignClient注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例,所以需要额外指定这个日志的级别才可以。

Feign支持4种日志级别:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。


全局配置

方式一:配置文件属性实现

feign:
  client:
    config:
      default:    # 将调用的微服务名称设置为default即为配置成全局
        loggerLevel: FULL

方式一:代码实现

//在启动类上为@EnableFeignClients注解添加defaultConfiguration配置
@EnableFeignClients(defaultConfiguration = FeignConfig.class)


细粒度(指定服务配置)

方式一:配置文件实现

feign:
  client:
    config:
      server-1:        # 想要调用的微服务名称
        loggerLevel: FULL

方式二:代码实现

1)编写配置类,定义日志级别

@Configuration
public class FeignConfig {
    @Bean
    Logger.Level level(){
        return Logger.Level.FULL;
    }
}

2)在FeignClient中指定配置类:(可以省略)

@FeignClient(value = "user-service", configuration = FeignConfig.class)
// 添加FeignClient,指定服务ID
public interface UserClient {
    @GetMapping("/user/{id}")
    User getById(@PathVariable("id") Long id);
}


Feign每次访问的日志示例:

image-20200916145905856.png

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
19天前
|
JSON Java 数据格式
【微服务】SpringCloud之Feign远程调用
本文介绍了使用Feign作为HTTP客户端替代RestTemplate进行远程调用的优势及具体使用方法。Feign通过声明式接口简化了HTTP请求的发送,提高了代码的可读性和维护性。文章详细描述了Feign的搭建步骤,包括引入依赖、添加注解、编写FeignClient接口和调用代码,并提供了自定义配置的示例,如修改日志级别等。
44 1
|
2月前
|
Java 数据库连接 数据库
spring复习05,spring整合mybatis,声明式事务
这篇文章详细介绍了如何在Spring框架中整合MyBatis以及如何配置声明式事务。主要内容包括:在Maven项目中添加依赖、创建实体类和Mapper接口、配置MyBatis核心配置文件和映射文件、配置数据源、创建sqlSessionFactory和sqlSessionTemplate、实现Mapper接口、配置声明式事务以及测试使用。此外,还解释了声明式事务的传播行为、隔离级别、只读提示和事务超时期间等概念。
spring复习05,spring整合mybatis,声明式事务
|
2月前
|
负载均衡 Java Nacos
SpringCloud基础2——Nacos配置、Feign、Gateway
nacos配置管理、Feign远程调用、Gateway服务网关
SpringCloud基础2——Nacos配置、Feign、Gateway
|
2月前
|
前端开发 API 微服务
SpringCloud微服务之间使用Feign调用不通情况举例
SpringCloud微服务之间使用Feign调用不通情况举例
422 2
|
2月前
|
Java API 开发者
【已解决】Spring Cloud Feign 上传文件,提示:the request was rejected because no multipart boundary was found的问题
【已解决】Spring Cloud Feign 上传文件,提示:the request was rejected because no multipart boundary was found的问题
314 0
|
4月前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
Spring Cloud Alibaba 发布了 Scheduling 任务调度模块 [#3732]提供了一套开源、轻量级、高可用的定时任务解决方案,帮助您快速开发微服务体系下的分布式定时任务。
14894 31
|
3月前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
4月前
|
负载均衡 Java Spring
Spring cloud gateway 如何在路由时进行负载均衡
Spring cloud gateway 如何在路由时进行负载均衡
473 15
|
4月前
|
Java Spring
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
109 3