Feign基本用法-Feign

什么是Feign

  • Feign是Spring Cloud Netflix组件中的一个轻量级RESTFULL的http服务客户端,实现了负载均衡和Rest调用的开源框架,封装了Ribbon和RestTemplate,实现了webservice的面向接口编程,进一步降低了项目的耦合度。
  • Feign内置了Ribbon,用来做客户端负载均衡调用服务注册中心的服务。
  • Feign本身并不支持SpringMVC的注解,它有一套自己的注解,为了更方便的使用,Spring Cloud孵化了OpenFeign。
  • Feign是一种申明式、模板化的HTTP客户端,可以让提供者无感知,消费者申明一下即可。
  • Feign支持的注解和用法参考官方文档:https://github.com/OpenFign/feign或者spring.io官网。
  • Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。

Feign解决什么问题

  • Feign旨在是编写java http客户端变得更加容易,Feign简化了RestTemplate代码,实现了Ribbon负载均衡,使代码变得更加简洁,也少了客户端调用的代码,使用Feign实现负载均衡是首先方案。只需要你创建一个接口,然后在上面添加注解即可。
  • Feign是声明式服务调用组件,其核心就是:像调用本地方法一样调用远程方法,无感知远程HTTP请求。
    1. 它解决了让开发者调用远程接口就跟调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是HTTP请求。无需关注与远程的交互细节,更无需关注分布式环境开发。
    2. 它像Dubbo一样,Consumer直接调用Provider接口方法,而不需要通过常规的Http Client构造请求再解析返回数据。
  • Feign vs OpenFeign
    1. OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如果@RequstMapping、@Pathvariable等等。
    2. OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实效类,实效类中做负载均衡并调用服务。

Feign入门

参考SpringCloud整合Feign(三)
搭建provider、consumer、api接口模块。

Feign使用步骤

  1. 消费者添加Feign依赖。
  2. 创建业务接口,添加@FeignClient注解声明需要调用的服务。
  3. 接口中的抽象方法使用SpringMVC注解配置服务地址及参数。
  4. 启动类添加@EnableFeignClients注解激活Feign组件,主要加上api接口包路径。

@FeignClient注解

  • name:指定Feign Client的名称,如果项目中使用了Ribbon,name属性会作为微服务的名称,用于服务发现。
  • url:url一般用于调试,可以手动指定@FeignClient调用的地址。
  • decode404:当发生404错误时,如果该字段为true,会调用decoder进行解码,否则抛出FeignException异常。
  • configuration:Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract(契约)。
  • fallback:定义容错的处理类,当调用远程接口失败或者超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口。
  • fallbackFactory:工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码。
  • path:定义当前FeignClient的统一前缀(一般用于服务提供者指定了服务路径前缀,例如:server.servlet.context-path=/test)。
    示例:
    application.properties
spring.application.name=feign-service
server.servlet.context-path=/feign-test

定义对外接口

@RequestMapping("/api/v1/group")
@FeignClient(value = "feign-service", path = "feign-test")
public interface TestApi {

    @ApiOperation("添加素材")
    @PostMapping("add")
    String add(@RequestBody @Validated String req);
}

Feign负载均衡

搭建负载均衡环境

  1. 拷贝之前的provider模块,修改模块名称为providerV2,构成两个服务提供者,负载均环境搭建完毕。
  2. 负载均衡环境下载地址如下:
    springcloud-feign使用DEMO

负载均衡用法

  1. 可以同时开启provider、providerV2两个服务,使用上面的注解,负载均衡策略为轮询策略,两个provider被调用的几率都是50%。
@FeignClient(value = "service-provider", contextId="OpenApi")
  1. 我们平时测试的时候可能只想指定一个服务被调用,可以通过增加url参数的方式,指定具体的ip地址和端口即可。
@FeignClient(value = "service-provider", contextId="OpenApi", url = "http://192.168.176.1:8080")

Feign请求传参

请求头

使用@RequestHeader注解参数。

GET

使用@PathVariable注解(路径变量)或者@RequestParam注解(问号后面的键值对参数)接收请求参数。

POST

使用@RequestParam注解(请求body中表单格式参数)、@RequestBody注解(请求体中raw类型json等格式的参数)接收请求参数。

Feign性能优化

Gzip压缩

  1. gzip介绍:gzip是一种数据格式,采用deflate算法压缩数据;gzip是一种流行的文件压缩算法,应用十分广泛,尤其是在linux平台。
  2. gzip能力:当Gzip压缩一个纯文本文件时,效果时非常明显的,大约可以减少70%以上的文件大小。
  3. gzip作用:网络数据经过压缩后实际上降低了网络传输的字节数,最明显的好处就是可以加快网页加载的速度。网页加载速度加快的好处不言而喻,除了节省流量,改善用户的浏览体验外,另一个潜在的好处是Gzip于搜索引擎的抓取工具邮政更好的关系。例如Goole就可以通过直接读取gzip文件来比普通手工抓取更快的检索网页。
    下面是百度的请求响应格式示例:
    Feign基本用法-Feign_第1张图片

HTTP协议关于压缩传输的规定

  1. 客户端的服务器请求中带有:Accept-Encoding:gzip,deflate字段,向服务器表示客户端支持的压缩格式(gzip或者deflate),如果不发生该消息头,服务端默认是不会压缩的。
  2. 服务端在收到请求后,如果发现请求头中含有Accept-Encoding:gzip消息头,表示服务端要根据该格式对响应报文进行压缩,压缩后返回给客户端并且携带Content-Encoding:gzip消息头,表示响应报文是根据该格式进行压缩的。
  3. 客户端接收到响应后,先判断响应头是否有Content-Encoding消息头,如果有,按照该格式解压报文,否则按正常报文处理。

Gzip压缩案例

局部压缩

只配置Consumer通过Feign到Provider的请求于响应的Gzip压缩。
消费者application.properties的配置

# Feign gzip压缩
# 请求开启gzip压缩
feign.compression.request.enabled=true
# 配置压缩支持的MIME TYPE
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 配置压缩数据大小的最小阈值,默认2048
feign.compression.request.min-request-size=512
# 响应开启gzip压缩
feign.compression.response.enabled=true

全局压缩

对客户浏览器的请求已经Consumer对Provider的请求与响应都是些Gzip压缩。
消费者application.properties配置

# 全局gzip压缩
# 开启压缩
server.compression.enabled=true
# 配置压缩支持的MIME TYPE
server.compression.mime-types=application/json,application/xml,text/xml,text/plain

HTTP连接池

为什么HTTP连接池能提升性能?

HTTP的背景原理

  • 两台服务器建立HTTP连接的过程是很复杂的,涉及到多个数据包的交换,很耗时。
  • HTTP连接需要的3次握手4次挥手开销很大,这一开销对于大量的比较小的HTTP消息来说更大。

解决方案

采用http连接池,可以节约大量的3次握手4次挥手,这样能大大提升吞吐量。
Feign的HTTP客户端支持3中框架:HttpURLConnection、HttpClient、OkHttp;默认是HttpURLConnection。可以通过查看源码org.springframework.cloud.openfeign.ribbo.FeignRibbonClientAutoConfiguration.java得知。

  • 传统的HttpURLConnection是JDK自带的,并不支持连接池,如果要实现连接池的机制,还需要自己来管理连接对象。对于网络请求这种底层相对复杂的操作,如果有可用的其他方案,没有必要自己去管理连接对象。
  • httpClient相比传统JDK自带的HttpURLConnection,它封装了访问HTTP的请求头,参数,内容体,响应等等;它不仅使客户端发送HTTP请求变得容易,而且也方便了开发人员测试接口(基于HTTP协议的),既提高了开发的效率,又提高了代码的健壮性;另外高并发大量的请求网络的时候,也是用“连接池”提升吞吐量。

HttpClient

将Feign的Http客户端工具修改为HttpClient。

添加依赖

修改Consumer项目,添加两个依赖,我们使用的SpringCloud版本已经默认集成了apache httpclient依赖,所以只需要添加一个依赖即可。

        
        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpclientartifactId>
        dependency>
        
        <dependency>
            <groupId>io.github.openfeigngroupId>
            <artifactId>feign-httpclientartifactId>
        dependency>

配置文件

# 使用apache httpclient作为默认的http请求工具
feign.httpclient.enabled=true

PS:如果使用HttpClient作为Feign的客户端工具,那么在定义接口上的注解需要注意的是:如果传递的参数是一个自定义的对象(对象会使用JSON的格式来传递),需要配置参数类型,例如:@GetMapping(value="/single/pojo", consumes=MediaType.APPLICATION_JSON_VALUE)。本文中使用的Spring Cloud版本(版本较新),已无需手动配置,并且使用了HttpClient客户端以后,我们还可以通过GET请求传递对象参数。

状态查看

浏览器发起的请求我们借助F12 Devtools中的Network来查看请求和响应信息。对于微服务中每个接口(多个微服务之间调用)我们又该如何查看URL,状态码和耗时信息?我们可以使用配置日志的方式进行查看。

logback.xml配置

Consumer项目添加logback.xml日志文件,内容如下(logback日志的输出解绑需要是DEBUG级别):




<configuration scan="true">

    
    <property name="CATALINA_BASE" value="E:\logs">property>

    
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder charset="UTF-8">
            
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
        encoder>
    appender>

    
    <appender name="FILE1" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            
            <fileNamePattern>${CATALINA_BASE}/aa.%d{yyyyMMdd}.logfileNamePattern>
            
            <maxHistory>30maxHistory>
        rollingPolicy>
        <encoder charset="UTF-8">
            
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
        encoder>
    appender>

    
    <appender name="FILE2" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${CATALINA_BASE}/bb.logfile>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${CATALINA_BASE}/bb.%d{yyyyMMdd}.logfileNamePattern>
            <maxHistory>30maxHistory>
        rollingPolicy>
        <encoder charset="UTF-8">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
        encoder>
    appender>

    <appender name="CUSTOM" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${CATALINA_BASE}/custom.logfile>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            
            <fileNamePattern>${CATALINA_BASE}/custom.%d{yyyy-MM-dd}.logfileNamePattern>
            
            <maxHistory>30maxHistory>
        rollingPolicy>
        <encoder charset="UTF-8">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
        encoder>
    appender>

    <logger name="file1" level="DEBUG">
        <appender-ref ref="FILE1" />
    logger>
    <logger name="file1" level="INFO">
        <appender-ref ref="FILE2" />
    logger>
    
    <logger name="custom" level="INFO">
        <appender-ref ref="CUSTOM" />
    logger>
    
    <root level="DEBUG">
        <appender-ref ref="CONSOLE" />
    root>
configuration>

全局

consumer项目启动类中注入Feign的Logger对象。

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = {"com.springcloud.feign.api"})
public class SpringcloudFeignConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudFeignConsumerApplication.class, args);
    }

    /**
     * Logger是Feign中的Logger类。
     * NONE:不记录任何信息,默认值。
     * BASIC:记录请求方法、请求URL、状态码和用时
     * HEADERS:在BASIC基础上再记录一些常用信息
     * FULL:记录请求和响应的所有信息
     *
     */
    @Bean
    public Logger.Level getLog() {
        return Logger.Level.FULL;
    }
}

局部

consumer项目application.properties中指定服务开启状态查看

# 局部指定服务进行状态查看
# service-provider为请求的服务名称
feign.client.config.service-provider.logger-level=FULL

测试

2021-06-20 15:54:24.215 [http-nio-8081-exec-1] DEBUG o.s.web.servlet.DispatcherServlet - GET "/consumer/hello?like=booksjf", parameters={masked}
2021-06-20 15:54:24.219 [http-nio-8081-exec-1] DEBUG o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped to com.springcloud.feign.springcloudfeignconsumer.controller.ConsumerTest#helloTest(String)
2021-06-20 15:54:24.259 [http-nio-8081-exec-1] DEBUG o.a.h.c.protocol.RequestAuthCache - Auth cache not set in the context
2021-06-20 15:54:24.262 [http-nio-8081-exec-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection request: [route: {}->http://192.168.195.1:8080][total available: 0; route allocated: 0 of 50; total allocated: 0 of 200]
2021-06-20 15:54:24.273 [http-nio-8081-exec-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {}->http://192.168.195.1:8080][total available: 0; route allocated: 1 of 50; total allocated: 1 of 200]
2021-06-20 15:54:24.274 [http-nio-8081-exec-1] DEBUG o.a.h.impl.execchain.MainClientExec - Opening connection {}->http://192.168.195.1:8080
2021-06-20 15:54:24.276 [http-nio-8081-exec-1] DEBUG o.a.h.i.c.DefaultHttpClientConnectionOperator - Connecting to /192.168.195.1:8080
2021-06-20 15:54:24.277 [http-nio-8081-exec-1] DEBUG o.a.h.i.c.DefaultHttpClientConnectionOperator - Connection established 192.168.195.1:65242<->192.168.195.1:8080
2021-06-20 15:54:24.277 [http-nio-8081-exec-1] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 60000
2021-06-20 15:54:24.277 [http-nio-8081-exec-1] DEBUG o.a.h.impl.execchain.MainClientExec - Executing request GET /echo/booksjf HTTP/1.1
2021-06-20 15:54:24.277 [http-nio-8081-exec-1] DEBUG o.a.h.impl.execchain.MainClientExec - Target auth state: UNCHALLENGED
2021-06-20 15:54:24.278 [http-nio-8081-exec-1] DEBUG o.a.h.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /echo/booksjf HTTP/1.1
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Encoding: gzip
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Encoding: deflate
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept: */*
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Content-Length: 0
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: 192.168.195.1:8080
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 >> Connection: Keep-Alive
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.13 (Java/1.8.0_181)
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "GET /echo/booksjf HTTP/1.1[\r][\n]"
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip[\r][\n]"
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: deflate[\r][\n]"
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept: */*[\r][\n]"
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Content-Length: 0[\r][\n]"
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: 192.168.195.1:8080[\r][\n]"
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.13 (Java/1.8.0_181)[\r][\n]"
2021-06-20 15:54:24.280 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
2021-06-20 15:54:24.283 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]"
2021-06-20 15:54:24.284 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Type: text/plain;charset=UTF-8[\r][\n]"
2021-06-20 15:54:24.284 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Length: 29[\r][\n]"
2021-06-20 15:54:24.284 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Date: Sun, 20 Jun 2021 07:54:24 GMT[\r][\n]"
2021-06-20 15:54:24.284 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Keep-Alive: timeout=60[\r][\n]"
2021-06-20 15:54:24.284 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Connection: keep-alive[\r][\n]"
2021-06-20 15:54:24.284 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
2021-06-20 15:54:24.284 [http-nio-8081-exec-1] DEBUG org.apache.http.wire - http-outgoing-0 << "Hello Nacos Discovery booksjf"
2021-06-20 15:54:24.286 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200 
2021-06-20 15:54:24.286 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Type: text/plain;charset=UTF-8
2021-06-20 15:54:24.286 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Length: 29
2021-06-20 15:54:24.286 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Sun, 20 Jun 2021 07:54:24 GMT
2021-06-20 15:54:24.286 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 << Keep-Alive: timeout=60
2021-06-20 15:54:24.286 [http-nio-8081-exec-1] DEBUG org.apache.http.headers - http-outgoing-0 << Connection: keep-alive
2021-06-20 15:54:24.290 [http-nio-8081-exec-1] DEBUG o.a.h.impl.execchain.MainClientExec - Connection can be kept alive for 60000 MILLISECONDS
2021-06-20 15:54:24.305 [http-nio-8081-exec-1] DEBUG o.s.w.c.HttpMessageConverterExtractor - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
2021-06-20 15:54:24.306 [http-nio-8081-exec-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection [id: 0][route: {}->http://192.168.195.1:8080] can be kept alive for 60.0 seconds
2021-06-20 15:54:24.306 [http-nio-8081-exec-1] DEBUG o.a.h.i.c.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
2021-06-20 15:54:24.306 [http-nio-8081-exec-1] DEBUG o.a.h.i.c.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {}->http://192.168.195.1:8080][total available: 1; route allocated: 1 of 50; total allocated: 1 of 200]
2021-06-20 15:54:24.315 [http-nio-8081-exec-1] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Using 'text/html', given [text/html, application/xhtml+xml, image/avif, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
2021-06-20 15:54:24.316 [http-nio-8081-exec-1] DEBUG o.s.w.s.m.m.a.RequestResponseBodyMethodProcessor - Writing ["Hello Nacos Discovery booksjf"]
2021-06-20 15:54:24.324 [http-nio-8081-exec-1] DEBUG o.s.web.servlet.DispatcherServlet - Completed 200 OK

请求超时

分布式项目中,服务压力比较大的情况下,可能处理服务的过程需要花费一定的时间,而默认情况下请求超时的配置是1s,所以我们需要调整该配置延长请求超时时间。

全局

# feign客户端请求超时设置
# 请求连接的超时时间,默认为2s
feign.httpclient.connection-timeout=1000

局部

feign.client.config.service-provider.connect-timeout=1000
# 请求处理的超时时间
feign.client.config.service-provider.read-timeout=1000

你可能感兴趣的:(通信框架,spring,cloud,java,spring)