天天看点

SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索

作者:暖杯巧克力aya

背景

SpringCloud OpenFeign 支持3种 HTTP client 来发送请求,分别是:

  • HttpURLConnection 默认形式,Java提供。
  • Apache HttpClient 5
  • okhttp

我们使用的 HttpURLConnection 即默认形式,预期提升请求时效,结合推荐,替换成 okhttp 测试效果。

okhttp 介绍

okhttp 有如下特性优势:

  • 支持 HTTP/2
  • 使用 HTTP 连接池 (HTTP/2 不可用)
  • 使用GZIP 压缩
  • 对响应缓存
  • 连接恢复,如果访问的服务有多个地址,那么连接一个地址失败后,重试其他地址

我们可使用到的特性是 HTTP 连接池和连接恢复。

实现

代码设置

1.在 pom.xml 文件中引入 okhttp 的包。

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

2.在 Spring 的配置文件中,设置开启 okhttp

feign:
  okhttp:
    enabled: true           

测试验证

调试 Feign 请求的代码,可以看到请求发送已经使用了 okhttp。

SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索

性能比较

对一个请求,其中实现中包含对另一个服务接口的 Feign 调用,进行 ab 压测,使用100个并发,发送10000次请求,比较其耗时。

1. HttpURLConnection 耗时情况

SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索

2. okhttp 耗时情况

SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索

可以看到请求时效的提升。

遇到的问题

1. method GET must not have a request body

Caused by: java.lang.IllegalArgumentException: method GET must not have a request body.
	at okhttp3.Request$Builder.method(Request.kt:258)           

是因为 okhttp 框架要求 GET 请求不能有 requestbody 。当方法设置 `@RequestMapping` 而未设置 method 时,使用 GET 请求。

修改方案:1. POST 方法时,在 @RequestMapping 上设置 method 参数。2. GET 方法时,参数使用 @RequestParam 而不是 @RequestBody。

okhttp 关于 GET 请求是否支持 requestbody ,可在 github issue 区查看讨论。

2. jar 包版本冲突报错

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [okhttp3.OkHttpClient$Builder]: Factory method 'okHttpClientBuilder' threw exception; nested exception is java.lang.NoSuchFieldError: Companion
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
	... 111 common frames omitted
Caused by: java.lang.NoSuchFieldError: Companion
	at okhttp3.internal.Util.<clinit>(Util.kt:70)
	at okhttp3.internal.concurrent.TaskRunner.<clinit>(TaskRunner.kt:309)
	at okhttp3.ConnectionPool.<init>(ConnectionPool.kt:41)
	at okhttp3.ConnectionPool.<init>(ConnectionPool.kt:47)           

是因为有其他 jar 包引入了 okhttp ,版本不同,底层实现的方法有不同。通过 IDE 查看各依赖。

SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索

修改方案:因为其他 jar 包引入的 okhttp 版本较低,升级高版本不可用。该服务采用不开启 okhttp,修改服务的 pom 文件不引入 okhttp 包。因为我们是基础的通用依赖,使用 exclusion 排除。

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

Feign 请求开启 HTTP/2 探索

okhttp 支持 HTTP/2,那么我们使用 HTTP/2 有什么好处呢?在 HTTP/2 中,当客户端请求数据时,服务器会一次性将多个数据流发送到客户端,而不是逐个依次发送,这种数据传输方法成为多路复用。同一个主机的所有请求共用一个 socket 连接。

HTTP/2 的类型

1.h2 (HTTP/2 over TLS)

2.h2c (HTTP/2 over TCP)

h2 必须要开启 SSL,也就是基于 HTTPS。而 h2c 可以不开启 SSL。

server 端开启 HTTP/2

Spring 对 h2 和 h2c 都支持。开启 HTTP/2 通过配置文件开关即可。添加如下配置:

server:
  http2:
    enabled: true           

服务启动后,发送请求查看到,验证服务端开启了 HTTP/2 。

`curl -I --http2 localhost:8885/***`

SpringCloud OpenFeign 使用 okhttp 发送 HTTP 请求与 HTTP/2 探索

Feign请求的客户端开启 HTTP/2

1.okhttp 支持 HTTP/2,但是只支持 h2 的形式。目前我们项目并未引入 SSL,所以不可行。框架也无支持 h2c 的计划,相关讨论在github issue 区。

2.HttpURLConnection 在 HTTP/2 协议全面改动后,这个类无法升级支持。Java 是提供了一套新的类来支持 HTTP/2,它们是 HttpClient。而 OpenFeign 的接入还在 snapshot 版本,SpringCloud 接入还需要等待。

GitHub github = Feign.builder()
                     .client(new Http2Client())
                     .target(GitHub.class, "https://api.github.com");           
<parent>
    <groupId>io.github.openfeign</groupId>
    <artifactId>parent</artifactId>
    <version>12.2-SNAPSHOT</version>
  </parent>           

3.Apache HttpClient 5 启用该 http client 后,打开 Spring 的 HTTP/2 开关,发起请求并不是 HTTP/2 的形式,h2c 形式未成功。

如上 SpringCloud OpenFeign 开启 HTTP/2 均未成功,打算等 OpenFeign 12.2发布后再测试。