Open Feign

Open Feign

在前面的学习中,我们使用了Ribbon的负载均衡功能,简化了远程调用时的代码:

String user = this.restTemplate.getForObject("http://spring-provider/provider/" + id, String.class);

如果就学到这里,可能以后需要编写类似的大量重复代码,格式基本相同,无非参数不一样。有没有更优雅的方式,来对这些代码再次优化呢?这就是我们接下来要学的Feign的功能了。

简介

Netflix 提供了 Feign,并在 2016.7 月的最后一个版本 8.18.0 之后,将其捐赠给 spring cloud 社区,并更名为 OpenFeign 。OpenFeign 的第一个版本就是 9.0.0 ,OpenFeign 会完全代理 HTTP 的请求,在使用过程中我们只需要依赖注入 Bean,然后调用对应的方法传递参数即可。这对程序员而言屏蔽了 HTTP 的请求响应过程,让代码更趋近于『调用』的形式。

Feign是一个远程调用组件,集成了ribbon和hystrix。把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。

在这里插入图片描述

入门

导入依赖

1、引入依赖

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

2、启动类增加@EnableFeignClients

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 开启feign客户端,无需配置熔断器和负载均衡注解
public class SpringConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringConsumerApplication.class, args);
    }
}

启动类增加了新的注解: @EnableFeignClients,如果我们要使用 Feign(声明式 HTTP 客户端),必须要在启动类加入这个注解开启 Feign 。

这样,我们的 Feign 就已经集成完成了,那么如何通过 Feign 去调用之前我们写的 HTTP 接口呢?

首先创建一个接口 ApiService(名字任意),并且通过注解配置要调用的服务地址。

Feign的客户端

@FeignClient(value = "spring-provider") // 标注该类是一个feign接口
public interface ProviderOpenfeign {

    @GetMapping("/sms/provider/{id}")
    String provider(@PathVariable("id") String id);
}
  • 这是一个接口,Feign会通过动态代理,帮我们生成实现类。这点跟mybatis的mapper很像;
  • @FeignClient,声明这是一个Feign客户端,类似@Mapper注解。同时通过value属性指定服务名称;
  • 接口中定义的方法,完全采用SpringMVC的注解,Feign会根据注解帮我们生成URL,并访问获取结果;
  • 一个服务只能被一个类绑定,不能让多个类绑定同一个远程服务,否则,会在启动项目是出现『已绑定』异常。

说明:

1、方法的返回值、参数以及requestmapping路径要和提供方的一模一样和消费方无关,消费方只需要调用该方法得到结果;
2、如果服务方controller类有@RequestMapping 定义命名空间,provider,在这里我们建议在方法上面拼接,不能在ProviderOpenfeign接口上去定义@requestMapping("/provider/");
3、@PathVariable("id") 这个括号里面的id不能省略,同理@Requestparam("id")里面的id也不能省略。

改造原来的调用逻辑,调用ProviderOpenfeign接口:

@Controller
public class ConsumerController {

    @Autowired
    private ProviderOpenfeign providerOpenfeign;

	@RequestMapping("/consumerOpenfeign/{id}")
    public String consumerOpenfeign(@RequestParam("id") String id){
        String consumer = providerOpenfeign.provider(id);
        return "consumerOpenfeign " + consumer;
    }
}

Hystrix支持

OPen Feign默认也有对Hystrix的集成:

在这里插入图片描述

默认是关闭的,我们需要通过下面的参数来开启:

feign:
  hystrix:
    enabled: true # 开启Feign的熔断功能,默认超时1s

只需要开启hystrix的熔断功能即可,默认时间是1s中,如果要修改熔断的时间,要做如下的配置:

feign:
  hystrix:
    enabled: true # 开启Feign的熔断功能
  client:
    config:
      default:
        connectTimeout: 4000  #设置feign超时连接时长
        readTimeout: 4000  #设置feign请求的超时时长  4s之后 提供方没有响应 直接降级
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000  
          strategy: THREAD
        timeout:
          enabled: true

说明:

connectTimeout是feign连接某个服务的超时时间,

readTimeout是feign请求某个接口的超时时长,

timeoutInMilliseconds是hystrix熔断的时间,

Hystrix的超时时间是站在命令执行时间来看的和Feign设置的超时时间在设置上并没有关联关系。Hystrix不仅仅可以封装Http调用,还可以封装任意的代码执行片段。Hystrix是从命令对象的角度去定义,某个命令执行的超时时间,超过此此时间,命令将会直接熔断,假设hystrix 的默认超时时间设置了3000(3秒),而feign 设置的是4秒,那么Hystrix会在3秒到来时直接熔断返回,不会等到feign的4秒执行结束,如果hystrix的超时时间设置为6s,而feign 设置的是4秒,那么最终要以feign的时间4s为准,你可以理解为以时间短的为准,这两段都要配置,不能不配hystrix的配置,否则feign配置的readTimeout不生效。

Feign中的Fallback降级配置不像hystrix中那样了。

1)首先,我们要定义一个类ProviderOpenfeignFallback,实现刚才编写的ProviderOpenfeign,作为fallback的处理类

@Component
public class ProviderOpenfeignFallback implements ProviderOpenfeign {

    @Override
    public User provider(String id) {
        return "服务器繁忙,请稍后再试!";
    }
}

2)然后在ProviderOpenfeign中,指定刚才编写的实现类

@FeignClient(value = "spring-provider",fallback = ProviderOpenfeignFallback.class) // 标注该类是一个feign接口
public interface ProviderOpenfeign {

    @GetMapping("/provider/{id}")
    String provider(@PathVariable("id") String id);
}

3)修改消费方controller的consumerOpenfeign方法,注释掉**@HystrixCommand注解和@DefaultProperties(defaultFallback=“fallback”)**进行测试。

@RestController
//@DefaultProperties(defaultFallback = "fallbackMethod")
public class ConsumerController {
    @Autowired
    private ProviderOpenfeign providerOpenfeign;

    @RequestMapping(value = "/consumerOpenfeign/{id}")
	//@HystrixCommand
    public String consumerOpenfeign(@PathVariable String id){
        String consumer = providerOpenfeign.provider(id);
        return "consumerOpenfeign consumer " + consumer;
    }
}

4)重启测试:

在这里插入图片描述

超时和超时重试

OpenFeign 本身也具备重试能力,在早期的 Spring Cloud 中,OpenFeign 使用的是 feign.Retryer.Default#Default() ,重试 5 次。但 OpenFeign 集成了Ribbon依赖和自动配置(默认也是轮询),Ribbon 也有重试的能力,此时,就可能会导致行为的混乱。(总重试次数 = OpenFeign 重试次数 x Ribbon 的重试次数,这是一个笛卡尔积。)

后来 Spring Cloud 意识到了此问题,因此做了改进(issues 467),将 OpenFeign 的重试改为 feign.Retryer#NEVER_RETRY ,即默认关闭。 Ribbon的重试机制默认配置为0,也就是默认是去除重试机制的,如果两者都开启重试,先执行ribbon重试,抛出异常之后再执行feign的重试。

所以,OpenFeign『对外』表现出的超时和重试的行为,实际上是 Ribbon 的超时和超时重试行为。我们在项目中进行的配置,也都是配置 Ribbon 的超时和超时重试

在调用方配置如下

# 全局配置
ribbon:
  # 请求连接的超时时间
  connectTimeout: 1000
  # 请求处理的超时时间
  readTimeout: 1000   #1秒
  # 最大重试次数
  MaxAutoRetries: 5
  # 切换实例的重试次数
  MaxAutoRetriesNextServer: 1
  #NFLoadBalancerRuleClassName: RandomRule
  # 对所有请求开启重试,并非只有get 请求才充实。一般不会开启这个功能。该参数和上面的3三个参数没有关系
  #okToRetryOnAllOperations: true
feign:
  hystrix:
    enabled: true     #默认是1s降级
  #client:         
    #config:
     # default:
       # connectTimeout: 4000  #要关掉feign超时连接时长
        #readTimeout: 150000
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 100000   #这个时间要大于ribbon重试次数的总时长,否则还没重试完就降级了

被调用方设置线程睡眠

@RestController
public class ProviderController {

    @Value("${server.port}")
    private String port;

    @RequestMapping(value = "/provider/{id}")
    public String provider(@PathVariable String id){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            return "exception:" + e.getMessage();
        }
        return "provider id = " + id + "port = " + port;
    }
}

测试结果:由于openfeign重试默认是关闭的,我们不用管它。被调用方,如果只启动一个被调方实例,则一共12次,因为 MaxAutoRetriesNextServer: 1 切换下一个实例再重试,下一个实例还是自己,如果被调方启动两个实例,则各6次。另外重试和熔断都开启,超时时间是1s,一共是12次,也就是12s,12s之后就会降级,而hystrix配置的timeoutInMilliseconds的15s降级,在这里是以时间短的为主。如果不对timeoutInMilliseconds进行配置,那么hystrix默认是1s,也就是1s钟之后就会降级,但是不影响ribbon的重试。

你也可以指定对某个特定服务的超时和超时重试:

则其他的请求走上面的重试,spring-provider该服务的重试单独配置

# 针对 spring-provide 的设置,注意服务名是小写
spring-provide:
  ribbon:
    connectTimeout: 1000
    readTimeout: 3000   
    MaxAutoRetries: 2
    MaxAutoRetriesNextServer: 2

在被调方,修改如下代码测试

@RestController
public class ProviderController {

    @Value("${server.port}")
    private String port;

    @RequestMapping(value = "/provider/{id}")
    public String provider(@PathVariable String id){
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            return "exception:" + e.getMessage();
        }
        return "provider id = " + id + "port = " + port;
    }
}

替换底层用HTTP实现

本质上是 OpenFeign 所使用的 RestTemplate 替换底层 HTTP 实现

  • 替换成 HTTPClient

将 OpenFeign 的底层 HTTP 客户端替换成 HTTPClient 需要 2 步:

1、引入依赖:

<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-httpclient</artifactId>
 </dependency>

2、在配置文件中启用它:

feign:
  httpclient:
    enabled: true # 激活 httpclient 的使用
  • 替换成 OkHttp

将 OpenFeign 的底层 HTTP 客户端替换成 OkHttp 需要 2 步:

1、引入依赖

<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>

2、配置

feign:
  httpclient:
    enabled: false  # 关闭 httpclient 的使用
  okhttp:
    enabled: true   # 激活 okhttp 的使用

日志级别(了解)

前面讲过,通过logging.level.xx=debug来设置日志级别。然而这个对Fegin客户端而言不会产生效果。因为@FeignClient注解修改的客户端在被代理时,都会创建一个新的Fegin.Logger实例。我们需要额外指定这个日志的级别才可以。然后根据 logging.level. 参数配置格式来开启 Feign 客户端的 DEBUG 日志,其中 部分为 Feign 客户端定义接口的完整路径。默认值是NONE,而NONE不会记录Feign调用过程的任何日志的,也就是说这个日志不是启动feign客户端的日志,而是feign调用远程接口时产生的日志

1)设置com.woniu包下的日志级别都为debug

logging:
  level:
    com:
      woniu:
        openfeign:
          ProviderOpenfeign: debug   #ProviderOpenfeign为某个feign接口

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

内容:

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

这里指定的Level级别是FULL,Feign支持4种级别:

在这里插入图片描述

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

3)在FeignClient中指定配置类:

@FeignClient(value = "spring-provider",fallback = ProviderOpenfeignFallback.class, configuration = FeignLogConfiguration.class)
public interface ProviderOpenfeign {

    @GetMapping("/provider/{id}")
    String provider(@PathVariable("id") String id);
}

4)重启项目,进行测试:在通过openfeign去调用某个接口时,会有详细的信息。如果把日志级别设置为NONE,则没有。

测试:http://localhost:8280/consumerOpenfeign

ml
logging:
level:
com:
woniu:
openfeign:
ProviderOpenfeign: debug #ProviderOpenfeign为某个feign接口


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

内容:

```java
@Configuration
public class FeignLogConfiguration {
    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
}
```

这里指定的Level级别是FULL,Feign支持4种级别:

[外链图片转存中...(img-IP80Hi1k-1694490245931)]

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

3)在FeignClient中指定配置类:

```java
@FeignClient(value = "spring-provider",fallback = ProviderOpenfeignFallback.class, configuration = FeignLogConfiguration.class)
public interface ProviderOpenfeign {

    @GetMapping("/provider/{id}")
    String provider(@PathVariable("id") String id);
}
```

4)重启项目,进行测试:在通过openfeign去调用某个接口时,会有详细的信息。如果把日志级别设置为NONE,则没有。

测试:http://localhost:8280/consumerOpenfeign 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖成范德彪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值