纠正了:Feign 默认不用 短连接

Feign 默认不是 短连接

疯狂创客圈 Java 高并发【 亿级流量聊天室实战】实战系列 【博客园总入口 】


疯狂创客圈(笔者尼恩创建的高并发研习社群)Springcloud 高并发系列文章,将为大家介绍三个版本的 高并发秒杀:

一、版本1 :springcloud + zookeeper 秒杀

二、版本2 :springcloud + redis 分布式锁秒杀

三、版本3 :springcloud + Nginx + Lua 高性能版本秒杀

以及有关Springcloud 几篇核心、重要的文章

一、Springcloud 配置, 史上最全 一文全懂

二、Springcloud 中 SpringBoot 配置全集 , 收藏版

三、Feign Ribbon Hystrix 三者关系 , 史上最全 深度解析

四、SpringCloud gateway 详解 , 史上最全

五、图解:tomcat的maxConnections、maxThreads、acceptCount | 秒懂

前言

网上很多文章都说,Feign 默认采用短连接进行远程调用,其实,这种结论是不对的。为什么呢? 下面细致的为大家来解读。

Feign中默认情况下的长连接

Feign中,默认情况下,使用的是JDK1.8中的 HttpURLConnection 基础连接类。该类的内部,使用了JDK1.8自带的 HttpClient 请求客户端类去负责完成底层的socket流操作。另外,JDK1.8还提供了一个简单的长连接缓冲类 KeepAliveCache,实现HttpClient 请求客户端类的缓存和复用。

三个类的所处位置为:HttpURLConnection 类处于 java.net 包中,而 HttpClient 类和KeepAliveCache类,则处于sun.net.www.http 包中。

HttpURLConnection、HttpClient、KeepAliveCache三个类的简单关系为:

每个HTTP请求都是一个HttpURLConnection实例,每个请求都会有一个 HttpClient 客户端实例,一个HttpClient 实例都持有一个TCP socket 长连接。如果 HttpClient 实例可以复用,则暂存在KeepAliveCache 缓存实例中。HttpURLConnection 会优先从缓存中取得合适的HttpClient 客户端,如果缓存中没有,HttpURLConnection 才会选择去创建新的HttpClient 实例。

通过三者之间的关系,可以看出: HttpClient 实例的复用,就是底层 TCP socket 长连接的复用。

JDK1.8 的 HttpClient 实例的复用的流程

下面,通过单步跟踪的方式,说一下HttpClient 实例的复用大致流程。

(1)首先,从默认的 feign.Client.Default 客户端的 execute 方法开始。

execute 方法首先会调用 convertAndSend(connection, request) 方法,打开一个URL连接实例,也即是一个 HttpURLConnection 类型的实例。然后,在convertResponse 方法中,开始获取响应码。这个时候,请求的整个处理过程,才真正开始。

纠正了:Feign 默认不用 短连接_第1张图片

(2) 通过单步跟踪发现,connection.getResponseCode() 语句执行过程中, 会通过调用HttpClient.New(…) 获取一个可用的 HttpClient 客户端实例。

HttpClient.New(…)是一个静态方法,大致的逻辑:它会调用 KeepAliveCache 类型的静态成员 kac 的get方法,去首先获取缓存中的HttpClient 客户端实例。

纠正了:Feign 默认不用 短连接_第2张图片

KeepAliveCache实例的 get方法,会主要以请求的 url 值作为 key,去缓存中查找是否有绑定的 HttpClient 实例,如果有的话直接拿过来用。

纠正了:Feign 默认不用 短连接_第3张图片

(3) 如果KeepAliveCache 缓存实例中没有,则调用HttpClient的构造器,新建一个HttpClient 对象,这个构造方法的最后一行,会调用了openServer() 方法,这个时候才会去真正的建立TCP连接。

纠正了:Feign 默认不用 短连接_第4张图片

(4) 至此,HttpURLConnection 实例的getResponseCode() 方法,终于拿到了内部的 HttpClient 连接,这个时候可以向 SERVER 端写请求数据了,这个时候会调用 writeRequests 方法。

HttpURLConnection 实例的writeRequests方法,首先会判断 httpClient.isKeepAlive 的值,该值默认是true,所以在请求上加上了 Connection:keep-alive 请求头 。

纠正了:Feign 默认不用 短连接_第5张图片

(5)writeRequests 方法写数据完成之后,会调用HttpClient.parseHTTP(…)方法,去解析服务端响应的数据,包括服务的响应头。

在parseHTTP(…)方法中,如果响应头中包含了Connection:keep-alive,并设置了Keep-Alive 头,比如含有以下内容:“Keep-Alive:timeout=xx,max=xxx” ,其中 timeout 表示服务端的‘空闲’超时时间,max表示长连接最多处理多少个请求。则这两个值,将覆盖掉 httpClient对象的keepAliveTimeout 和 keepAliveConnections 属性的值。

(6) parseHTTP(…)方法读取完数据之后,最终会调用到httpClient.finished方法,将当前httpClient对象,加入到缓存中,这个地方是实现 TCP 连接复用的关键。
纠正了:Feign 默认不用 短连接_第6张图片

(7)HttpClient的 putInKeepAliveCache方法,主要以请求的 url 值作为 key (因为这里的第二个参数总是写死为 null ),以当前 HttpClient 实例为 value,放入到KeepAliveCache 类型的静态成员 kac 缓存中,以便后面进行复用 。

纠正了:Feign 默认不用 短连接_第7张图片

通过以上的七步,JDK1.8 实现了HttpURLConnection 的长连接。

这里,有一个问题:什么Feign调用会默认在请求头中加上Connection:keep-alive?

原因是这样的,在 sun.net.www.http.HttpClient 类的静态初始化代码部分,包括了 keepAliveProp 静态属性的初始化。keepAliveProp 静态属性的值,是决定HttpClient 客户端实例是否复用的关键之一,如果keepAliveProp 静态属性值为false,则无论如何都不会进行连接复用。sun.net.www.http.HttpClient 类的静态初始化代码节选如下:

 static {
        String var0 = (String)AccessController.doPrivileged(new GetPropertyAction("http.keepAlive"));
        //…省略不相干代码
        if (var0 != null) {
            keepAliveProp = Boolean.valueOf(var0);
        } else {
            keepAliveProp = true;  //默认值为true
        }
         //…省略不相干代码
}

可以看到,首先取得一个系统配置项 http.keepAlive 的值,如果该配置项的值没有做专门的设置或者修改,则sun.net.www.http.HttpClient 静态属性 keepAliveProp 的值,默认被赋值为true。

通过阅读源码可以知道,静态属性 keepAliveProp 的值,是决定HttpClient 的实例对象是否放入长连接缓冲池 KeepAliveCache 的一个重要关键属性值。也就是说,这个属性为true,则 HttpClient 实例对象具备复用的可能,否则,HttpClient 实例对象不能被复用。

至此,关于JDK1.8 实现如何实现长连接,也就介绍完了。不过,细心的读者可能会发现,HttpURLConnection 内部的长连接复用,和URL有关:只有在URL字符串相同的情况下,才能进行复用。这就有一个问题,如果URL中带有变量值,比如 /order/1/detail、/order/2/detail ,则不同的参数,不能进行HttpClient 实例对象的复用。

JDK默认的HttpClient 实例对象的复用的问题

和ApacheHttpClient 连接复用相比,JDK默认的HttpClient 实例对象的复用,有以下问题:

(1)JDK默认的 HttpClient 实例对象复用的粒度太小,只有URL相同的情况下,才能进行连接复用。而 ApacheHttpClient 连接复用的粒度则大很多,同路由的连接,就可以复用。

(2)在URL字符串变化比较大的场景下,JDK默认的 HttpClient 实例对象的内部连接,会保持一段时间才被释放,会占用系统的连接资源,更加不利于高并发。

所以,从以上两点出发,由于不能相同保证URL的请求数据巨大,所以不建议使用JDK默认的HttpClient 实例对象。建议在Feign中,使用ApacheHttpClient 连接池进行连接的复用

最后,介绍一下疯狂创客圈:疯狂创客圈,一个Java 高并发研习社群 【博客园 总入口 】

疯狂创客圈,倾力推出:面试必备 + 面试必备 + 面试必备 的基础原理+实战 书籍 《Netty Zookeeper Redis 高并发实战》

img


疯狂创客圈 Java 死磕系列

  • Java (Netty) 聊天程序【 亿级流量】实战 开源项目实战
  • Netty 源码、原理、JAVA NIO 原理
  • Java 面试题 一网打尽
  • 疯狂创客圈 【 博客园 总入口 】

ty) 聊天程序【 亿级流量】实战 开源项目实战**

  • Netty 源码、原理、JAVA NIO 原理
  • Java 面试题 一网打尽
  • 疯狂创客圈 【 博客园 总入口 】

你可能感兴趣的:(java)