本文从WordPress迁移而来, 查看全部WordPress迁移文章
背景:要从内部的一个web api拉取数据,要请求大概15万次左右(15万个ID,每个ID请求一次)
特点:一个host(也可以以IP直接请求),ID是作为请求参数发过去的。
一开始是非常简单地使用 requests.get(“htttp://www.myapi.com”),也没发现多大的问题;
后来就出现了错误 (‘Connection aborted.’, error(99, ‘Cannot assign requested address’)),数量巨大
问一下搜索引擎这个错误的原因是什么,基本的说法就是:
客户端频繁的连服务器,由于每次连接都在很短的时间内结束,导致很多的TIME_WAIT,以至于用光了可用的端口号,所以新的连接没办法绑定端口,即“Cannot assign requested address”
看到这些解释的时候,一点都不冤枉,正好是我的请求,请求了15万次,每次请求都是短连接,很短时间结束;
netstat -an | grep TIME_WAIT 查看一下TIME_WAIT的情况,果然数量巨大,而且目的IP都是同一个(host的IP)。
然后在同学的指导下用了requests的连接池,确实也符合我的需求场景。我只向一个host发请求,变的只是请求参数,所以HTTP连接完全可以使用长连接;
也就是先向服务器建立好几十个连接(多一点应该问题也不大,我只建立了24个),在这些TCP连接上传输我们的HTTP报文。
最后的结果就是再没出现过上面的错误了,然后我们再用netstat看一下
netstat -an | grep host的IP
看到了 24条记录,都是 ESTABLISHED 状态;符合预期,就是那24条连接一直持续着
代码如下
1
2
3
4
5
6
7
8
9def __get_http_session(self, pool_connections, pool_maxsize, max_retries):
session = requests.Session()
# 创建一个适配器,连接池的数量pool_connections, 最大数量pool_maxsize, 失败重试的次数max_retries
adapter = requests.adapters.HTTPAdapter(pool_connections = pool_connections,
pool_maxsize = pool_maxsize, max_retries = max_retries)
# 告诉requests,http协议和https协议都使用这个适配器
session.mount('http://', adapter)
session.mount('https://', adapter)
return session
后续的使用也是很直接的,最简单的使用是 requests.get, requests.post, requests.head 等,这里我们就直接 session.get(), session.post(), session.head() 即可
session就是 __get_http_session() 返回的对象
后面同学又问了一个问题,这个问题是因为端口号用完而导致的,要是能修改内核,增加端口号行不行呢?而后同学马上又说了,这是不行的,因为且不说修改linux内核行不行,但TCP协议中是有字段来标识端口号的,16位,所以端口号最大只能到65535,这不是由操作系统决定的,是TCP协议决定的。
linux能修改的,是缩短TIME_WAIT的时间
这个连接池的用法适合这种背景,向一个host频繁发请求
但对于大量不同的host发请求,是没什么效果的,因为不可能建立长连接(不同host,一般IP都不同,连接是以源IP:源端口号,目的IP:目的端口号标识的)