OkHttpClient 最早用于安卓客户端请求服务端场景,优势是请求稳定,低延迟,甚至可以多数据中心负载均衡。
特性:
- 支持 HTTP/2,对于相同的 host 会共享一个 socket 连接
- 在不支持HTTP/2,情况下,实现连接池,从而有效降低建立连接的请求延迟
- 响应缓存,避免重复请求
- 支持同步或异步请求
连接
参考文档:
https://square.github.io/okhttp/connections/
https://www.cnblogs.com/duanxz/p/11066227.html
Address
定义了目标站点的域名(类似:github.com),所有的静态配置,端口号,以及通信协议。
在同一个地址下的 URL,可能会共享同一个 TCP 连接,OKHttp 使用连接池来自动管理回收这些连接。
Routes
对同一个 Address 可能会有多个 Routes,因为同一个域名可能会对应多个服务器 IP 地址。
发起连接和请求流程
当你使用 OkHttpClient 发一起一个 URL 请求时,它会有以下逻辑:
- 使用URL中的scheme, hostname, port,以及 OkHttpClient 的配置信息,创建了一个 Address,这个Address定义了我们应该怎么连接到服务器。
- 尝试从连接池(connection pool)中,复用一个以 Address 定义的连接
- 如果没找到可用连接,它会选择一个 Route ,(通常是通过DNS反查服务器的IP地址等等),一个host 可能对应多个服务器的IP。
- 如果是一个新的 Route,他会直接创建一个 socket 连接。
- 发送 Http 请求,并且接收响应。
如果连接出现了问题,OkHttp 会尝试另一个 Route
如果接收到响应,这个连接将会返回到连接池中以备未来使用,连接池会清理一段时间过期的连接。
实例化
OkHttpClient 实例应该是共享的。
应该创建一个独立的 OkHttpClient 实例发起所有请求,因为每个 OkHttpClient 会拥有 独立的连接池和线程池。复用连接和线程能够有效的降低延迟减少内存开销。
可以使用以下两种方式创建实例:
1 | x = new OkHttpClient() |
然后,通过 newBuilder() 定制 共享实例,会构建一个派生的 client,使用同一个连接池和线程池
1 | x = client |
问题总结
Gzip
Okhttp 通过查看源码,如果用户没有指定 Accept-Encoding 的 header,会默认发送 Gzip 的头,同时自动解压缩。这个操作是在拦截器中做的。
如果用户手动配置这个头,则需要手动解压缩。
同时还会自动发送 Keep-Alive, User-Agent 等 header。
源码位置:
1 | okhttp3.internal.http.BridgeInterceptor.Java |
连接池泄露问题(重要)
每次都实例化新的 OkHttpClient 的实例,又没有使用共享的连接池,这样在实例化时每次默认都会创建一个新的连接池。从而导致连接泄露。
内存可能泄露的问题(重要)
虽然每次使用共享的连接池,但是每次都实例化新的 OkHttpClient 实例,会导致内存回收不够及时,FullGC 频繁导致内存占用巨大。(可能有内存泄漏问题)
连接池大小的配置(重要)
连接池第一个参数:最大空闲连接,不是指总共的连接池大小。
而是针对某个 Address 的最大空闲连接。针对同一个站点,如有多个IP对照的情况(baidu.com),会创建很多的Address,从而导致连接池崩溃,内存崩溃。
不同场景的测试结果分析
请求内网地址(100线程共请求5W次)
- 每次都 new okhttpclient 实例化的场景,FullGC 频繁:
- 200M JVM内存,连接池 (10,10);连接数(21,57),new okhttpclient ,71 FullGC,1200 GC,总时间 28秒,成功率100%
- 200M JVM内存,连接池 (1,10);连接数(13,15),new okhttpclient ,51FullGC,1141 GC,总时间54秒,成功率80%
- 200M JVM内存,连接池 (5,10);连接数(8,12),new okhttpclient ,71FullGC,1188 GC,总时间28秒,成功率100%
- 200M JVM内存,连接池 (5,60);连接数(14,29),new okhttpclient ,72FullGC,1194GC,总时间28秒,成功率100%
- 200M JVM内存,连接池 (50,10);连接数(100,100),new okhttpclient ,71FullGC,1197GC,总时间4秒,成功率100%
- 使用 newbuilder 实例化场景:
- 200M JVM内存,连接池 (10,10);连接数(88,100),newbuilder,0FullGC,133 GC,总时间 4秒,成功率100%
- 200M JVM内存,连接池 (1,10);连接数(84,98),newbuilder,0FullGC,131 GC,总时间4秒,成功率100%
- 200M JVM内存,连接池 (5,10);连接数(90,99),newbuilder,0FullGC,128GC,总时间5秒,成功率100%
- 200M JVM内存,连接池 (5,60);连接数(86,98),newbuilder,0FullGC,136 GC,总时间4秒,成功率100%
- 200M JVM内存,连接池 (50,10);连接数(93,99),newbuilder,0FullGC,135 GC,总时间4秒,成功率100%
- 200M JVM内存,连接池 (5,10);连接数(95, 100),not new,0FullGC,130GC,总时间4秒,成功率100%