前文:

最近在搞全网的CDN刷新系统,在性能调优时遇到了requests长连接的一个问题,以前关注过长连接太多造成浪费的问题,但因为系统都是分布式扩展的,针对这种各别问题就懒得改动了。 现在开发的缓存刷新系统,对于性能还是有些敏感的,我后面会给出最优的http长连接池构建方式。

老生常谈:

python下的httpclient库哪个最好用? 我想大多数人还是会选择requests库的。原因么?也就是简单,易用!

如何蛋疼的构建reqeusts的短连接请求:

python requests库默认就是长连接的 (http 1.1, Connection: keep alive),如果单纯在requests头部去掉Connection是不靠谱的,还需要借助httplib来配合.

s = requests.Session()

del s.headers['Connection']

正确发起 http 1.0的请求姿势是:

#xiaorui.cc

import httplib

import requests

httplib.HTTPConnection._http_vsn = 10

httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0'

r = requests.get('http://127.0.0.1:8888/')

服务端接收的http包体内容:

GET / HTTP/1.0

Accept-Encoding: gzip, deflate

Accept: */*

User-Agent: python-requests/2.5.1 CPython/2.7.10 Darwin/15.4.0

所谓短连接就是发送 HTTP 1.0 协议,这样web服务端当然会在send完数据后,触发close(),也就是传递 \0 字符串,达到关闭连接 ! 这里还是要吐槽一下,好多人天天说系统优化,连个基本的网络io都不优化,你还想干嘛。。。下面我们依次聊requests长连接的各种问题及性能优化。

那么requests长连接如何实现?

requests给我们提供了一个Session的长连接类,他不仅仅能实现最基本的长连接保持,还会附带服务端返回的cookie数据。 在底层是如何实现的?

把HTTP 1.0 改成 HTTP 1.1 就可以了, 如果你标明了是HTTP 1.1 ,那么有没有 Connection: keep-alive 都无所谓的。 如果 HTTP 1.0加上Connection: keep-alive ,那么server会认为你是长连接。 就这么简单 !

poll([{fd=5, events=POLLIN}], 1, 0) = 0 (Timeout)

sendto(5, "GET / HTTP/1.1\r\nHost: www.xiaorui.cc\r\nConnection: keep-alive\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nUser-Agent: python-requests/2.9.1\r\n\r\n", 144, 0, NULL, 0) = 144

fcntl(5, F_GETFL) = 0x2 (flags O_RDWR)

fcntl(5, F_SETFL, O_RDWR) = 0

Session的长连接支持多个主机么? 也就是我在一个服务里先后访问 a.com, b.com, c.com 那么requests session能否帮我保持连接 ?

答案很明显,当然是可以的!

但也仅仅是可以一用,但他的实现有很多的槽点。比如xiaorui.cc的主机上还有多个虚拟主机,那么会出现什么情况么? 会不停的创建新连接,因为reqeusts的urllib3连接池管理是基于host的,这个host可能是域名,也可能ip地址,具体是什么,要看你的输入。

strace -p 25449 -e trace=connect

Process 25449 attached - interrupt to quit

connect(13, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("61.216.13.196")}, 16) = 0

connect(8, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.202.72.116")}, 16) = 0

connect(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("125.211.204.141")}, 16) = 0

connect(8, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0

connect(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("153.37.238.190")}, 16) = 0

connect(8, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0

connect(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("157.255.128.103")}, 16) = 0

connect(8, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0

connect(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("139.215.203.190")}, 16) = 0

connect(8, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0

connect(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("42.56.76.104")}, 16) = 0

connect(8, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0

connect(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("42.236.125.104")}, 16) = 0

connect(8, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0

connect(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("110.53.246.11")}, 16) = 0

connect(8, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0

connect(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("36.248.26.191")}, 16) = 0

connect(8, {sa_family=AF_UNSPEC, sa_data="\0\0\0\0\0\0\0\0\0\0\0\0\0\0"}, 16) = 0

connect(8, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("125.211.204.151")}, 16) = 0

又比如你可能都是访问同一个域名,但是子域名不一样,例子 a.xiaorui.cc, b.xiaorui.cc, c.xiaorui.cc, xxxx.xiaorui.cc,那么会造成什么问题? 哪怕IP地址是一样的,因为域名不一样,那么requests session还是会帮你实例化长连接。

python 24899 root 3u IPv4 27187722 0t0 TCP 101.200.80.162:59576->220.181.105.185:http (ESTABLISHED)

python 24899 root 4u IPv4 27187725 0t0 TCP 101.200.80.162:54622->101.200.80.162:http (ESTABLISHED)

python 24899 root 5u IPv4 27187741 0t0 TCP 101.200.80.162:59580->220.181.105.185:http (ESTABLISHED)

python 24899 root 6u IPv4 27187744 0t0 TCP 101.200.80.162:59581->220.181.105.185:http (ESTABLISHED)

python 24899 root 7u IPv4 27187858 0t0 TCP localhost:50964->localhost:http (ESTABLISHED)

python 24899 root 8u IPv4 27187880 0t0 TCP 101.200.80.162:54630->101.200.80.162:http (ESTABLISHED)

python 24899 root 9u IPv4 27187921 0t0 TCP 101.200.80.162:54632->101.200.80.162:http (ESTABLISHED)

如果是同一个二级域名,不同的url会发生呢? 是我们要的结果,只需要一个连接就可以了。

import requests

import time

s = requests.Session()

while 1:

r = s.get('http://a.xiaorui.cc/1')

r = s.get('http://a.xiaorui.cc/2')

r = s.get('http://a.xiaorui.cc/3')

我们可以看到该进程只实例化了一个长连接。

# xiaorui.cc

python 27173 root 2u CHR 136,11 0t0 14 /dev/pts/11

python 27173 root 3u IPv4 27212480 0t0 TCP 101.200.80.162:36090->220.181.105.185:http (ESTABLISHED)

python 27173 root 12r CHR 1,9 0t0 3871 /dev/urandom

那么requests还有一个不是问题的性能问题。。。

requests session是可以保持长连接的,但他能保持多少个长连接? 10个长连接! session内置一个连接池,requests库默认值为10个长连接。

requests.adapters.HTTPAdapter(pool_connections=100, pool_maxsize=100)

一般来说,单个session保持10个长连接是绝对够用了,但如果你是那种social爬虫呢?这么多域名只共用10个长连接肯定不够的。

python 28484 root 3u IPv4 27225486 0t0 TCP 101.200.80.162:54724->103.37.145.167:http (ESTABLISHED)

python 28484 root 4u IPv4 27225349 0t0 TCP 101.200.80.162:36583->120.132.34.62:https (ESTABLISHED)

python 28484 root 5u IPv4 27225490 0t0 TCP 101.200.80.162:46128->42.236.125.104:http (ESTABLISHED)

python 28484 root 6u IPv4 27225495 0t0 TCP 101.200.80.162:43162->222.240.172.228:http (ESTABLISHED)

python 28484 root 7u IPv4 27225613 0t0 TCP 101.200.80.162:37977->116.211.167.193:http (ESTABLISHED)

python 28484 root 8u IPv4 27225413 0t0 TCP 101.200.80.162:40688->106.75.67.54:http (ESTABLISHED)

python 28484 root 9u IPv4 27225417 0t0 TCP 101.200.80.162:59575->61.244.111.116:http (ESTABLISHED)

python 28484 root 10u IPv4 27225521 0t0 TCP 101.200.80.162:39199->218.246.0.222:http (ESTABLISHED)

python 28484 root 11u IPv4 27225524 0t0 TCP 101.200.80.162:46204->220.181.105.184:http (ESTABLISHED)

python 28484 root 12r CHR 1,9 0t0 3871 /dev/urandom

python 28484 root 14u IPv4 27225420 0t0 TCP 101.200.80.162:42684->60.28.124.21:http (ESTABLISHED)

让我们看看requests的连接池是如何实现的? 通过代码很容易得出Session()默认的连接数及连接池是如何构建的? 下面是requests的长连接实现源码片段。如需要再详细的实现细节,那就自己分析吧

# xiaorui.cc

class Session(SessionRedirectMixin):

def __init__(self):

...

self.max_redirects = DEFAULT_REDIRECT_LIMIT

self.cookies = cookiejar_from_dict({})

self.adapters = OrderedDict()

self.mount('https://', HTTPAdapter()) # 如果没有单独配置adapter适配器,那么就临时配置一个小适配器

self.mount('http://', HTTPAdapter()) # 根据schema来分配不同的适配器adapter,上面是https,下面是http

self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE)

class HTTPAdapter(BaseAdapter):

def __init__(self, pool_connections=DEFAULT_POOLSIZE,

pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES,

pool_block=DEFAULT_POOLBLOCK):

if max_retries == DEFAULT_RETRIES:

self.max_retries = Retry(0, read=False)

else:

self.max_retries = Retry.from_int(max_retries)

self.config = {}

self.proxy_manager = {}

super(HTTPAdapter, self).__init__()

self._pool_connections = pool_connections

self._pool_maxsize = pool_maxsize

self._pool_block = pool_block

self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) # 连接池管理

DEFAULT_POOLBLOCK = False #是否阻塞连接池

DEFAULT_POOLSIZE = 10 # 默认连接池

DEFAULT_RETRIES = 0 # 默认重试次数

DEFAULT_POOL_TIMEOUT = None # 超时时间

Python requests连接池是借用urllib3.poolmanager来实现的。

每一个独立的(scheme, host, port)元祖使用同一个Connection, (scheme, host, port)是从请求的URL中解析分拆出来的。

from .packages.urllib3.poolmanager import PoolManager, proxy_from_url 。

下面是 urllib3的一些精简源码, 可以看出他的连接池实现也是简单粗暴的。

# 解析url,分拆出scheme, host, port

def parse_url(url):

"""

Example::

>>> parse_url('http://google.com/mail/')

Url(scheme='http', host='google.com', port=None, path='/mail/', ...)

>>> parse_url('google.com:80')

Url(scheme=None, host='google.com', port=80, path=None, ...)

>>> parse_url('/foo?bar')

Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...)

return Url(scheme, auth, host, port, path, query, fragment)

# 获取匹配的长连接

def connection_from_url(self, url, pool_kwargs=None):

u = parse_url(url)

return self.connection_from_host(u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs)

# 获取匹配host的长连接

def connection_from_host(self, host, port=None, scheme='http', pool_kwargs=None):

if scheme == "https":

return super(ProxyManager, self).connection_from_host(

host, port, scheme, pool_kwargs=pool_kwargs)

return super(ProxyManager, self).connection_from_host(

self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs)

# 根据url的三个指标获取连接

def connection_from_pool_key(self, pool_key, request_context=None):

with self.pools.lock:

pool = self.pools.get(pool_key)

if pool:

return pool

scheme = request_context['scheme']

host = request_context['host']

port = request_context['port']

pool = self._new_pool(scheme, host, port, request_context=request_context)

self.pools[pool_key] = pool

return pool

# 获取长连接的主入口

def urlopen(self, method, url, redirect=True, **kw):

u = parse_url(url)

conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme)

这里为止,Python requests关于session连接类实现,说的算明白了。 但就requests和urllib3的连接池实现来说,还是有一些提升空间的。 但问题来了,单单靠着域名和端口会造成一些问题,至于造成什么样子的问题,我在上面已经有详细的描述了。

那么如何解决?

我们可以用 scheme + 主domain + host_ip + port 来实现长连接池的管理。

其实大多数的场景是无需这么细致的实现连接池的,但根据我们的测试的结果来看,在服务初期性能提升还是不小的。

这样既解决了域名ip轮询带来的连接重置问题,也解决了多级域名下不能共用连接的问题。

以上这篇构建高效的python requests长连接池详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

python requests 异步调用_构建高效的python requests长连接池详解相关推荐

  1. [Python图像识别] 五十.Keras构建AlexNet和CNN实现自定义数据集分类详解

    该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门.OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子.图像增强技术.图像分割等,后期结合深度学习研究图像识别 ...

  2. python定义静态变量_对Pyhon实现静态变量全局变量的方法详解

    python不能像C++一样直接定义一个static变量或者通过extern来导入别的库的变量而实现数据共享,但是python的思想是通过模块化来解决这个问题,就是通过模块来实现全局变量. 首先新建一 ...

  3. Redis Lettuce客户端异步连接池详解

    前言 异步/非阻塞编程模型需要非阻塞API才能获得Redis连接.阻塞的连接池很容易导致阻塞事件循环并阻止您的应用程序进行处理的状态.Lettuce带有异步,非阻塞池实现,可与Lettuces异步连接 ...

  4. python requests 异步调用_带有Python请求的异步请求

    小编典典 注意 下面的答案是不适用于请求v0.13.0 +.编写此问题后,异步功能已移至grequests.但是,你可以将其替换requests为grequests下面的内容,它应该可以工作. 我已经 ...

  5. python 类可以调用实例变量_Python实例方法、类方法、静态方法区别详解

    1.关于参数的区别 实例方法:定义实例方法是最少有一个形参 ---> 实例对象,通常用 self 类方法:定义类方法的时候最少有一个形参 ---> 类对象,通常用 cls 静态方法:定义静 ...

  6. python中transpose函数_对numpy中的transpose和swapaxes函数详解

    transpose() 这个函数如果括号内不带参数,就相当于转置,和.T效果一样,而今天主要来讲解其带参数. 我们看如下一个numpy的数组: `arr=np.arange(16).reshape(( ...

  7. python做估值模型_通证估值模型-费雪模型与净现值模型详解

    通证估值模型-费雪模型与净现值模型详解 一.费雪模型,适合货币型通证 1)公式:MV= PQ M: 货币供应量 V: 货币流通速度 P:劳务平均价格 V:劳务总数 维基百科地址:Equation of ...

  8. python alpha beta 剪枝_一看就懂的 Alpha-Beta 剪枝算法详解

    Alpha-Beta剪枝用于裁剪搜索树中没有意义的不需要搜索的树枝,以提高运算速度. 假设α为下界,β为上界,对于α ≤ N ≤ β: 若 α ≤ β  则N有解. 若 α > β 则N无解. ...

  9. [Python从零到壹] 九.网络爬虫之Selenium基础技术万字详解(定位元素、常用方法、键盘鼠标操作)

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

最新文章

  1. java基础系列:集合基础(1)
  2. 编程的一些快捷键(转帖)
  3. 数据有了,如何构建数据资产?
  4. Android http 的使用
  5. 服务器系统使用30金手指,seo专业培训拾首选金手指三:自动设置锚文本的
  6. 只要200行JavaScript代码,就能把特斯拉汽车带到您身边
  7. C++实现通过UDP传输文件
  8. nosql和rdnms_用于SaaS和NoSQL的Jdbi
  9. Android 集成支付宝支付,支付宝支付2.0
  10. 数据集标签_数据分享 | LiDAR点云数据汇总
  11. 力特usb转232驱动程序下载_电脑USB接口、U盘接口不能使用的原因及解决方法
  12. 计算机网络介绍,TCP协议,Socket网络编程
  13. JSON在JS中的应用
  14. [含lw+源码等]微信小程序点餐|外卖|餐饮系统+后台管理系统[包运行成功]
  15. 《计算机网络 自顶向下》第一章==计算机网络和因特网==随堂笔记
  16. 计算机与网络安全经历了几个阶段,网络信息安全知识:根据互联网的发展阶段,互联网治理分为三个层面,即结构层面、功能层面、意识层面。确立网络规范属于互联网意识层面的治理。()...
  17. 软件测试之因果图分析
  18. 9个接私活的网站,你有码,我有钱
  19. linux中__weak关键字的作用
  20. Java面试题最新更新

热门文章

  1. LAMP集群项目五 nfs存储的数据实时同步到backupserver
  2. Windows的同步I/O和异步I/O
  3. 一个很奇特的异常 tmpFile.renameTo(classFile) failed
  4. uva 620 Cellular Structure
  5. Ubuntu: Ubuntu中安装使用Source Inside 3.5
  6. opj1837 Balance(dp)
  7. visual studio community 2019 编译调试linux环境下程序
  8. 小白使用Eclipse 的基本设置
  9. 解决Eclipse报错:the file dx.jar was not loaded from the SDK
  10. Android启动的init进程