深入研究golang net/http连接池可用性

前言:

    又要重构一个高频的http访问服务了,前人留下的老代码实现过程太过诡异,调用链太长,不能达到高内聚低耦合的特性。总的来说,老代码不可触碰,对的,不可触碰 !!! 我想这种感觉大家都知道…  那么这跟我们的标题有什么关系?项目关键的维度就是高频的http访问,这里必然要涉及到http连接池.  大家潜规则的会认为标准库里的http库肯定不好用,我先前也是这么考虑的,后来发现golang社区里的http client基本都是围绕net/http造轮子.  github中http client的轮子大多都是像python requests那样更加好用而已,而不是在功能和底层上有提升. 


    既然大家都在用net/http,或者是net/http的扩展,那么有必要深入测试下,别掉坑里。 先前使用python requests的时候就被坑了好几次,好在结合requests源代码找到了答案.  这篇主要说下测试net/http连接池的测试结果,下篇会聊聊go net/http连接池是怎么实现的,具体到源码方面的体现. 

该文章后续仍在不断更新中, 请移步到原文地址 http://xiaorui.cc/?p=5056

测试过程

net/http 长连接验证 ?

默认是长连接,毋庸置疑. 客户端发起的时候会在header里标记HTTP/1.1 .


net/http 连接复用 ?

连接可复用. 只要匹配到目标ip及端口就可以服用到该维度的连接池.


直接注释 response.Body.Close() 会出现什么?

各种循环测试,不仅长连接,而且连接还是会被复用. 社区里有人反映说close注释掉会出现连接不停创建的情况.


如果连接池中,某个主机的连接被占用,上层并发请求会发生什么?

net/http在池中找不到有用的连接,就会不断的重新new一个连接,不会阻塞等待一个连接. 

对端关闭,上层代码如果不管不问的会出现什么?

go runtime 会在底层一直帮你epoll wait, 监听读事件的close报文 (空值报文) ,接着自动帮你做close fd相关, 然后在上层标记出网络连接是否发生关闭. 哪怕你的逻辑只是成功发起请求后,一直等待的sleep下去。


通过Linux strace系统调用、tcpdump能看到fin的过程,可以用tcpdump把包导出到wireshark查看. 


  附带说一下,像python requests那样,他无法在上层判断得知socket是否关闭,也没有golang runtime帮你做事件的监听及变更,只能每次去请求之前,需要先非阻塞的调用poll 指定的fd,看看是否有读事件,看看是否为空值,如果有空值的话,直接close该连接,然后重新建立连接。 另外,看了python requests及redis-py pool的代码, 他们默认都会重试一次connect的过程.


下面是 连接被关闭后,net/http再次请求时发生的系统调用。 简单说,他也会重新建立一次连接。

  有不少人忽略的一点,只要内核的协议栈收到对方服务发起的fin请求,就会把连接的状态置为close_wait,至于什么时候去关闭释放连接要看你的httpclient库的代码实现。 time_wait的状态是谁发起关闭,那么谁的网络状态里就存在相应的time_wait连接.  短连接请求里大多是服务端根据你的keep-alive属性做是否关闭连接的逻辑,换句话说,多数是服务端发起关闭.


总结:

     net/http的连接池是默认全局共用的,你的后端主机虽然只有一百多台,如果我有100个协程,有概率会出现同时针对一主机并发访问,那么一个主机就有100个连接,100个后端主机就会产生10000个连接。 这问题的概率在我们生产环境中经常出现。100台没问题,那么更多呢?  单ip在主动连接可用的端口不到65535的…  所以,大家也要考虑到这问题。 

    我们当初的做法一开始是这样,进程的连接数做计数,当达到一定的阈值后,进行短连接请求,  但这样带来的问题是time wait过多,重复的建连效率也在下降。 golang net/http的MaxIdleConns也只是针对主机的维度,暂时没找到限制总连接数及主机连接池的控制入口。

     总的来说,golang net/http值得拥有,只需要简单封装下就可以顺溜溜的使用了。


对Golang感兴趣的朋友可以加群: 278517979 !!!
另外如果大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码, 感谢!
另外再次标注博客原地址  xiaorui.cc