net/http

在golang程序中,我也遇到因为不合理使用 http client导致的程序崩溃问题。

坑:1:默认的HttpClient

no timeout

假如发出请求的服务端API有问题:没有及时响应httpclient请求但是保持了连接, 在高并发情况下,打开的连接数会持续增长,最终导致客户端服务器资源到达瓶颈。

Timeout

HttpClient Timeout包括连接、重定向(如果有)、从Response Body读取的时间,内置定时器会在Get,Head、Post、Do 方法之后继续运行,直到读取完Response.Body。

这里有个有关HttpClient Timeout的排障问题,你可参考。

Timeout

坑2:默认的Http Transport池化机制

目前常见的HttpClient(.NET Core,golang) 都会有连接池的概念, 客户端会尽量复用池中已经建立的tcp连接 (sqlclient连接池也是复用的tcp连接)。

之前我有个误区,认为连接池是预置连接(因为有个开源作者实现的redis库是预置连接),其实不是的,连接池强调的是复用已创建的连接,连接池的创建是由首次请求来驱动的。

Transport
DefaultTransport is the default implementation of Transport and is used by DefaultClient. It establishes network connections as needed and caches them for reuse by subsequent calls. It uses HTTP proxies as directed by the HTTP_PROXY and NO_PROXY (or http_proxy and no_proxy) environment variables.

默认值: MaxIdleConns =100, MaxIdleConnsPerHost=2 ;

没有定义MaxConns字段,golang其实创建conn是无限制的, MaxConnsPerHost=0

http连接池化 ,是公共连接池, 能创建的连接是无限制的(虽然没字段,但是代码分析是无限制的), 每个Host能创建的连接MaxConnsPerHost=0 , 也是无限制的;

DefaultMaxIdleConnsPerHost=2

发现问题了吗? 能无限制创建,但是能复用的只有2个

这意味着:如果你的请求是高并发持续请求, 一开始请求能无限制创建, 但是由于不能复用tcp连接(2个,聊胜于无), 造成客户端主动关闭tcp连接,time_wait状态(2min)会占用大量端口, 之后就不能发起tcp连接了。

有些同学不知道威力,我画个图理解一下。

在并发持续请求host的情况下, 因为不能复用tcp连接,就会频繁销毁连接, 这样会累积很多 time_wait状态的不可用连接, 没过多久就创建不了了。

解决方案:不要使用默认Transport,增加MaxIdleConnsPerHost

--- 本人回顾了.NET HttpClient,不用刻意关闭这个值。

实际上,.NET也存在这个,但是.NET Core这个PerServer被设置为int.maxvalue,所以我们无需关注,.NET真香。

我的收获

通过本文,我们谈到了golang HttpClient的2个坑位、由坑位导致的现象和排障思路,各位看官,有则改之无则加勉。

MaxIdleConnsPerHost=2 基本就告别了连接复用