关于spring-cloud-alibaba:openFeign夺命连环9问

37次阅读

共计 9976 个字符,预计需要花费 25 分钟才能阅读完成。

1、前言

后面介绍了 Spring Cloud 中的灵魂摆渡者Nacos,和它的前辈们相比不仅仅功能强大,而且部署非常简单。

明天介绍一款服务调用的组件:OpenFeign,同样是一款超过先辈(RibbonFeign)的狠角色。

文章目录如下:

2、Feign 是什么?

Feign 也是一个狠角色,Feign 旨在使得 Java Http 客户端变得更容易。

Feign 集成了 Ribbon、RestTemplate 实现了负载平衡的执行 Http 调用,只不过对原有的形式(Ribbon+RestTemplate)进行了封装,开发者不用手动应用 RestTemplate 调服务,而是定义一个接口,在这个接口中标注一个注解即可实现服务调用,这样更加合乎面向接口编程的主旨,简化了开发。

但遗憾的是 Feign 当初进行迭代了,当然当初也是有不少企业在用。

有想要学习 Feign 的读者能够上 spring Cloud 官网学习,陈某这里也不再具体介绍了,不是明天的重点。

3、openFeign 是什么?

后面介绍过进行迭代的 Feign,简略点来说:OpenFeign 是 springcloud 在 Feign 的根底上反对了 SpringMVC 的注解,如 @RequestMapping 等等。OpenFeign 的 @FeignClient 能够解析 SpringMVC 的 @RequestMapping 注解下的接口,并通过动静代理的形式产生实现类,实现类中做负载平衡并调用其余服务。

官网地址:https://docs.spring.io/spring…

4、Feign 和 openFeign 有什么区别?

Feign openFiegn
Feign 是 SpringCloud 组件中一个轻量级 RESTful 的 HTTP 服务客户端,Feign 内置了 Ribbon,用来做客户端负载平衡,去调用服务注册核心的服务。Feign 的应用形式是:应用 Feign 的注解定义接口,调用这个接口,就能够调用服务注册核心的服务 OpenFeign 是 SpringCloud 在 Feign 的根底上反对了 SpringMVC 的注解,如 @RequestMapping 等。OpenFeign 的 @FeignClient 能够解析 SpringMVC 的 @RequestMapping 注解下的接口,并通过动静代理的形式产生实现类,实现类中做负载平衡并调用其余服务。

5、环境筹备

本篇文章 Spring Cloud 版本、JDK 环境、我的项目环境均和上一篇 Nacos 的环境雷同:五十五张图通知你微服务的灵魂摆渡者 Nacos 到底有多强?。

注册核心就不再应用 Eureka 了,间接应用 Nacos 作为注册和配置核心,有不会的能够查看 Nacos 文章。

本篇文章搭建的我的项目构造如下图:

注册核心应用Nacos,创立个微服务,别离为服务提供者Produce,服务消费者Consumer

6、创立服务提供者

既然是微服务之间的互相调用,那么肯定会有服务提供者了,创立openFeign-provider9005,注册进入 Nacos 中,配置如下:

server:
  port: 9005
spring:
  application:
    ## 指定服务名称,在 nacos 中的名字
    name: openFeign-provider
  cloud:
    nacos:
      discovery:
        # nacos 的服务地址,nacos-server 中 IP 地址: 端口号
        server-addr: 127.0.0.1:8848
management:
  endpoints:
    web:
      exposure:
        ## yml 文件中存在特殊字符,必须用单引号蕴含,否则启动报错
        include: '*'

留神 :此处的spring.application.name 指定的名称将会在 openFeign 接口调用中应用。

我的项目源码都会上传,对于如何注册进入 Nacos,增加什么依赖源码都会有,联合陈某上篇 Nacos 文章,这都不是难事!

7、创立服务消费者

新建一个模块 openFeign-consumer9006 作为消费者服务,步骤如下。

1、增加依赖

除了 Nacos 的注册核心的依赖,还要增加 openFeign 的依赖,如下:

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

2、增加注解 @EnableFeignClients 开启 openFeign 性能

老套路了,在 Spring boot 主启动类上增加一个注解@EnableFeignClients,开启 openFeign 性能,如下:

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OpenFeignConsumer9006Application
{public static void main(String[] args) {SpringApplication.run(OpenFeignConsumer9006Application.class, args);
    }
}

3、新建 openFeign 接口

新建一个 openFeign 接口,应用 @FeignClient 注解标注,如下:

@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {}

留神 :该注解@FeignClient 中的 value 属性指定了服务提供者在 nacos 注册核心的 服务名

4、新建一个 Controller 调试

新建一个 controller 用来调试接口,间接调用 openFeign 的接口,如下:

@RestController
@RequestMapping("/openfeign")
public class OpenFeignController {}

好了,至此一个 openFeign 的微服务就搭建好了,并未实现具体的性能,上面一点点实现。

8、openFeign 如何传参?

开发中接口传参的形式有很多,然而在 openFeign 中的传参是有肯定规定的,上面具体介绍。

1、传递 JSON 数据

这个也是接口开发中罕用的传参规定,在 Spring Boot 中通过 @RequestBody 标识入参。

provider 接口中 JSON 传参办法如下:

@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {@PostMapping("/order2")
    public Order createOrder2(@RequestBody Order order){return order;}
}

consumer 中 openFeign 接口中传参代码如下:

@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
    /**
     * 参数默认是 @RequestBody 标注的,这里的 @RequestBody 能够不填
     * 办法名称任意
     */
    @PostMapping("/openfeign/provider/order2")
    Order createOrder2(@RequestBody Order order);
}

留神:openFeign默认的传参形式就是 JSON 传参(@RequestBody),因而定义接口的时候能够不必 @RequestBody 注解标注,不过为了标准,个别都填上。

2、POJO 表单传参

这种传参形式也是比拟罕用,参数应用 POJO 对象接管。

provider 服务提供者代码如下:

@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {@PostMapping("/order1")
    public Order createOrder1(Order order){return order;}
}

consumer 消费者 openFeign 代码如下:

@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
    /**
     * 参数默认是 @RequestBody 标注的,如果通过 POJO 表单传参的,应用 @SpringQueryMap 标注
     */
    @PostMapping("/openfeign/provider/order1")
    Order createOrder1(@SpringQueryMap Order order);
}

网上很多人纳闷 POJO 表单形式如何传参,官网文档明确给出了解决方案,如下:

openFeign 提供了一个注解 @SpringQueryMap 完满解决 POJO 表单传参。

3、URL 中携带参数

此种形式针对 restful 形式中的 GET 申请,也是比拟罕用申请形式。

provider 服务提供者代码如下:

@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {@GetMapping("/test/{id}")
    public String test(@PathVariable("id")Integer id){return "accept one msg id="+id;}

consumer 消费者 openFeign 接口如下:

@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {@GetMapping("/openfeign/provider/test/{id}")
    String get(@PathVariable("id")Integer id);
}

应用注解 @PathVariable 接管 url 中的占位符,这种形式很好了解。

4、一般表单参数

此种形式传参不倡议应用,然而也有很多开发在用。

provider 服务提供者代码如下:

@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {@PostMapping("/test2")
    public String test2(String id,String name){return MessageFormat.format("accept on msg id={0},name={1}",id,name);
    }
}

consumer 消费者 openFeign 接口传参如下:

@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
    /**
     * 必须要 @RequestParam 注解标注,且 value 属性必须填上参数名
     * 办法参数名能够任意,然而 @RequestParam 注解中的 value 属性必须和 provider 中的参数名雷同
     */
    @PostMapping("/openfeign/provider/test2")
    String test(@RequestParam("id") String arg1,@RequestParam("name") String arg2);
}

5、总结

传参的形式有很多,比方文件传参 ….. 陈某这里只是列举了四种常见得传参形式。

9、超时如何解决?

想要了解超时解决,先看一个例子:我将 provider 服务接口睡眠 3 秒钟,接口如下:

@PostMapping("/test2")
public String test2(String id,String name) throws InterruptedException {Thread.sleep(3000);
        return MessageFormat.format("accept on msg id={0},name={1}",id,name);
}

此时,咱们调用 consumer 的 openFeign 接口返回后果如下图:

很显著的看出程序异样了,返回了接口调用超时。what?why?………..

openFeign 其实是有默认的超时工夫的,默认别离是连贯超时工夫 10 秒、读超时工夫60 秒,源码在feign.Request.Options#Options() 这个办法中,如下图:

那么问题来了:为什么我只设置了睡眠 3 秒就报超时呢?

其实 openFeign 集成了 Ribbon,Ribbon 的默认超时连接时间、读超时工夫都是是 1 秒,源码在 org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute() 办法中,如下图:

源码大抵意思:如果 openFeign 没有设置对应得超时工夫,那么将会采纳 Ribbon 的默认超时工夫。

了解了超时设置的原理,由之产生两种计划也是很明了了,如下:

  • 设置 openFeign 的超时工夫
  • 设置 Ribbon 的超时工夫

1、设置 Ribbon 的超时工夫(不举荐)

设置很简略,在配置文件中增加如下设置:

ribbon:
  # 值的是建设链接所用的工夫,实用于网络情况失常的状况下,两端链接所用的工夫
  ReadTimeout: 5000
  # 指的是建设链接后从服务器读取可用资源所用的工夫
  ConectTimeout: 5000

2、设置 openFeign 的超时工夫(举荐)

openFeign 设置超时工夫非常简单,只须要在配置文件中配置,如下:

feign:
  client:
    config:
      ## default 设置的全局超时工夫,指定服务名称能够设置单个服务的超时工夫
      default:
        connectTimeout: 5000
        readTimeout: 5000

default 设置的是全局超时工夫,对所有的 openFeign 接口服务都失效

然而失常的业务逻辑中可能波及到多个 openFeign 接口的调用,如下图:

上图中的伪代码如下:

public T invoke(){
    //1. 调用 serviceA
    serviceA();
    
    //2. 调用 serviceA
    serviceB();
    
    //3. 调用 serviceA
    serviceC();}

那么下面配置的全局超时工夫能不能通过呢?很显然是 serviceAserviceB 可能胜利调用,然而 serviceC 并不能胜利执行,必定报超时。

此时咱们能够给 serviceC 这个服务独自配置一个超时工夫,配置如下:

feign:
  client:
    config:
      ## default 设置的全局超时工夫,指定服务名称能够设置单个服务的超时工夫
      default:
        connectTimeout: 5000
        readTimeout: 5000
      ## 为 serviceC 这个服务独自配置超时工夫
      serviceC:
        connectTimeout: 30000
        readTimeout: 30000

留神:单个配置的超时工夫将会笼罩全局配置。

10、如何开启日志加强?

openFeign 尽管提供了日志加强性能,然而默认是不显示任何日志的,不过开发者在调试阶段能够本人配置日志的级别。

openFeign 的日志级别如下:

  • NONE:默认的,不显示任何日志;
  • BASIC:仅记录申请办法、URL、响应状态码及执行工夫;
  • HEADERS:除了 BASIC 中定义的信息之外,还有申请和响应的头信息;
  • FULL:除了 HEADERS 中定义的信息之外,还有申请和响应的注释及元数据。

配置起来也很简略,步骤如下:

1、配置类中配置日志级别

须要自定义一个配置类,在其中设置日志级别,如下:

留神:这里的 logger 是 feign 包里的。

2、yaml 文件中设置接口日志级别

只须要在配置文件中调整指定包或者 openFeign 的接口日志级别,如下:

logging:
  level:
    cn.myjszl.service: debug

这里的 cn.myjszl.service 是 openFeign 接口所在的包名,当然你也能够配置一个特定的 openFeign 接口。

3、演示成果

上述步骤将日志设置成了FULL,此时发出请求,日志成果如下图:

日志中具体的打印出了申请头、申请体的内容。

11、如何替换默认的 httpclient?

Feign 在默认状况下应用的是 JDK 原生的 URLConnection 发送 HTTP 申请,没有连接池,然而对每个地址会放弃一个长连贯,即利用 HTTP 的 persistence connection。

在生产环境中,通常不应用默认的 http client,通常有如下两种抉择:

  • 应用ApacheHttpClient
  • 应用OkHttp

至于哪个更好,其实各有千秋,我比拟偏向于 ApacheHttpClient,毕竟老牌子了,稳定性不在话下。

那么如何替换掉呢?其实很简略,上面演示应用 ApacheHttpClient 替换。

1、增加 ApacheHttpClient 依赖

在 openFeign 接口服务的 pom 文件增加如下依赖:

<!--     应用 Apache HttpClient 替换 Feign 原生 httpclient-->
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
    </dependency>
    
    <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-httpclient</artifactId>
    </dependency>

为什么要增加下面的依赖呢?从源码中不难看出,请看 org.springframework.cloud.openfeign.FeignAutoConfiguration.HttpClientFeignConfiguration 这个类,代码如下:

上述红色框中的生成条件,其中的 @ConditionalOnClass(ApacheHttpClient.class),必须要有ApacheHttpClient 这个类才会失效,并且 feign.httpclient.enabled 这个配置要设置为true

2、配置文件中开启

在配置文件中要配置开启,代码如下:

feign:
  client:
    httpclient:
      # 开启 Http Client
      enabled: true

3、如何验证曾经替换胜利?

其实很简略,在 feign.SynchronousMethodHandler#executeAndDecode() 这个办法中能够分明的看出调用哪个 client,如下图:

上图中能够看到最终调用的是ApacheHttpClient

4、总结

上述步骤仅仅演示一种替换计划,剩下的一种不再演示了,原理雷同。

12、如何通信优化?

在讲如何优化之前先来看一下GZIP 压缩算法,概念如下:

gzip 是一种数据格式,采纳用 deflate 算法压缩数据;gzip 是一种风行的数据压缩算法,利用非常宽泛,尤其是在 Linux 平台。

当 GZIP 压缩到一个纯文本数据时,成果是非常明显的,大概能够缩小 70%以上的数据大小。

网络数据通过压缩后实际上升高了网络传输的字节数,最显著的益处就是能够放慢网页加载的速度。网页加载速度放慢的益处显而易见,除了节俭流量,改善用户的浏览体验外,另一个潜在的益处是 GZIP 与搜索引擎的抓取工具有着更好的关系。例如 Google 就能够通过间接读取 GZIP 文件来比一般手工抓取更快地检索网页。

GZIP 压缩传输的原理如下图:

依照上图拆解出的步骤如下:

  • 客户端向服务器申请头中带有:Accept-Encoding:gzip,deflate 字段,向服务器示意,客户端反对的压缩格局(gzip 或者 deflate),如果不发送该音讯头,服务器是不会压缩的。
  • 服务端在收到申请之后,如果发现申请头中含有 Accept-Encoding 字段,并且反对该类型的压缩,就对响应报文压缩之后返回给客户端,并且携带 Content-Encoding:gzip 音讯头,示意响应报文是依据该格局压缩过的。
  • 客户端接管到响应之后,先判断是否有 Content-Encoding 音讯头,如果有,按该格局解压报文。否则按失常报文解决。

openFeign 反对 申请 / 响应 开启 GZIP 压缩,整体的流程如下图:

上图中波及到 GZIP 传输的只有两块,别离是Application client -> Application ServiceApplication Service->Application client

留神:openFeign 反对的 GZIP 仅仅是在 openFeign 接口的申请和响应,即是 openFeign 消费者调用服务提供者的接口。

openFeign 开启 GZIP 步骤也是很简略,只须要在配置文件中开启如下配置:

feign:
  ## 开启压缩
  compression:
    request:
      enabled: true
      ## 开启压缩的阈值,单位字节,默认 2048,即是 2k,这里为了演示成果设置成 10 字节
      min-request-size: 10
      mime-types: text/xml,application/xml,application/json
    response:
      enabled: true

上述配置实现之后,发出请求,能够分明看到申请头中曾经携带了 GZIP 压缩,如下图:

13、如何熔断降级?

常见的熔断降级框架有HystrixSentinel,openFeign 默认反对的就是Hystrix,这个在官网文档上就有体现,毕竟是一奶同胞嘛,哈哈 ………..

然而阿里的 Sentinel 无论是性能个性、简略易上手等各方面都齐全秒杀 Hystrix,因而此章节就应用 openFeign+Sentinel 进行整合实现服务降级。

阐明:此处并不着重介绍 Sentinel,陈某打算放在下一篇文章具体介绍 Sentinel 的弱小之处。

1、增加 Sentinel 依赖

openFeign-consumer9006 消费者的 pom 文件增加 sentinel 依赖(因为应用了聚合模块,不指定版本号),如下:

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

2、配置文件中开启 sentinel 熔断降级

要想 openFeign 应用 sentinel 的降级性能,还须要在配置文件中开启,增加如下配置:

feign:
  sentinel:
    enabled: true

3、增加降级回调类

这个类肯定要和 openFeign 接口实现同一个类,如下图:

OpenFeignFallbackService这个是降级回调的类,一旦 OpenFeignService 中对应得接口呈现了异样则会调用这个类中对应得办法进行降级解决。

4、增加 fallback 属性

@FeignClient 中增加 fallback 属性,属性值是降级回调的类,如下:

@FeignClient(value = "openFeign-provider",fallback = OpenFeignFallbackService.class)
public interface OpenFeignService {}

5、演示

通过如上 4 个步骤,openFeign 的熔断降级曾经设置实现了,此时演示下成果。

通过 postman 调用 http://localhost:9006/openfeign/order3 这个接口,失常逻辑返回如下图:

当初手动造个异样,在服务提供的接口中抛出异样,如下图:

此时从新调用http://localhost:9006/openfeign/order3,返回如下图:

哦豁,能够很分明的看到服务曾经胜利降级调用,哦了,性能实现。

留神:理论开发中返回后果应该依据架构对立定制,陈某这里只是为了演示不便,不要借鉴,哈哈。。。

14、总结

本篇文章次要面对初学者,深刻的源码以及熔断降级放在前面具体介绍,文中若有表述不清,谬误的中央欢送斧正!

这是陈某 Spring Cloud 进阶专栏的第二篇文章,感觉文章不错的,欢送点赞、珍藏、转发。

以上源码曾经上传 GitHub,须要的公号【码猿技术专栏】回复关键词 9528 获取。

正文完
 0