转译自:https://laike9m.com/blog/requests-secret-pool_connections-and-pool_maxsize,89/

Requests' secret: pool_connections and pool_maxsize

Requests 是一个python开发者众所周知的第三方库。因其简单的API和高性能,大多数人倾向于使用requests而不是urllib2作为访问http的标准库。然而很多使用requests库的人可能不知道内部原因,今天我就来解释以下俩个概念: pool_connections 和pool_maxsize.

从 Session 开始:

import requests

s = requests.Session()
s.get('https://www.google.com')

这很简单,你可能知道requests' Session 持有cookie. 但你知道 Session 有一个 mount 方法吗?

mount(prefix, adapter)
Registers a connection adapter to a prefix.
Adapters are sorted in descending order by key length.

不知道?很好,实际上你已经使用了这个方法当你初始化一个session对象时:

class Session(SessionRedirectMixin):

    def __init__(self):
...
# Default connection adapters.
self.adapters = OrderedDict()
self.mount('https://', HTTPAdapter())
self.mount('http://', HTTPAdapter())

现在,到了有趣的部分。如果你阅读过 Ian Cordasco's 文章 Retries in Requests, 你应该知道 HTTPAdapter 可以提供重试功能. 但一个 HTTPAdapter 真实是什么? Quoted from doc:

class requests.adapters.HTTPAdapter(pool_connections=10, pool_maxsize=10, max_retries=0, pool_block=False)

The built-in HTTP Adapter for urllib3.

Provides a general-case interface for Requests sessions to contact HTTP and HTTPS urls by implementing the Transport Adapter interface. This class will usually be created by the Session class under the covers.

Parameters:

  • pool_connections – The number of urllib3 connection pools to cache.
  • pool_maxsize – The maximum number of connections to save in the pool.
  • max_retries(int) – The maximum number of retries each connection should attempt. Note, this applies only to failed DNS lookups, socket connections and connection timeouts, never to requests where data has made it to the server. By default, Requests does not retry failed connections. If you need granular control over the conditions under which we retry a request, import urllib3’s Retry class and pass that instead.
  • pool_block – Whether the connection pool should block for connections. Usage:
>> import requests
>> s = requests.Session()
>> a = requests.adapters.HTTPAdapter(max_retries=3)
>> s.mount('http://', a)

如果上面的文档使你迷惑,这里是我的解释: HTTP Adapter 所做的就是:根据不同的目标url,对每个不同的请求提供不同的配置,还记得上面的代码吗?

    what HTTP Adapter does is simply providing different configurations for different requests according to target url. Remember the code above?

self.mount('https://', HTTPAdapter())
self.mount('http://', HTTPAdapter())

它创建了俩个 HTTPAdapter 对象使用默认的参数:pool_connections=10, pool_maxsize=10, max_retries=0, pool_block=False, 并且挂载到 https:// 和 http:// 相对路径, 意思是第一个配置在你访问 http://xxx 会使用到, 第二个会在你访问 https://xxx. 尽管上面俩个配置是相同的,请求http和https仍然是隔离的。我们会在下面看到说明。

就像我说的,这篇文章的目的主要是解释 pool_connections and pool_maxsize.

首先,让我们看看 pool_connections. 昨天我在stackoverflow提出了一个问题:question ,因为我不确定我是否理解正确,这个问题的回答消除了我的不确定性。HTTP,众所周知,是基于TCP协议,一个HTTP连接也是一个TCP连接,它是由一个五元组唯一标识:

(<protocol>, <src addr>, <src port>, <dest addr>, <dest port>)

就是说,你针对 www.example.com 建立了一个HTTP/TCP连接, 假设服务器支持 Keep-Alive, 下一次你发送请求到 www.example.com/a or www.example.com/b, 你可以使用相同的连接,因为五元组没有改变. 实际上, requests' Session 自动会帮你处理 并且重用连接,只要它能.

这个问题是,什么决定了你是否可以重用老的连接?是的,就是 pool_connections!

pool_connections – The number of urllib3 connection pools to cache.

我知道,我知道,我也不想引入很多术语, 这是最后一个,我发誓. 简单的理解它, 一个host对应的一个连接池:one connection pool corresponds to one host, 嗯,就是这个意思.

这是一个例子,无关的行已被忽略:

s = requests.Session()
s.mount('https://', HTTPAdapter(pool_connections=1))
s.get('https://www.baidu.com')
s.get('https://www.zhihu.com')
s.get('https://www.baidu.com') """output
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.baidu.com
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 200 None
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.zhihu.com
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 200 2621
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.baidu.com
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 200 None
"""

HTTPAdapter(pool_connections=1) 被映射到 https://, 他意思是在同一时间仅有一个连接池. 在调用 s.get('https://www.baidu.com'), 缓存的连接池是 connectionpool('https://www.baidu.com'). 现在 s.get('https://www.zhihu.com') 来了,这个session发现他不能使用缓存的连接,因为他不是相同的host(one connection pool corresponds to one host, remember?). 因此session不得不创建一个新的连接池. 因为 pool_connections=1, session不能在同一时间持有俩个连接池, 因此他丢弃了旧的连接池: connectionpool('https://www.baidu.com') 并且保存了新的:connectionpool('https://www.zhihu.com'). 下一次get同样如此Next get is the same. 这就是我们看见三个 Starting new HTTPS connection 日志行的原因

如果我们设置 pool_connections 为 2:

s = requests.Session()
s.mount('https://', HTTPAdapter(pool_connections=2))
s.get('https://www.baidu.com')
s.get('https://www.zhihu.com')
s.get('https://www.baidu.com')
"""output
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.baidu.com
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 200 None
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.zhihu.com
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 200 2623
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 200 None
"""

很好,现在我们可以创建连接俩次了并且保持连接活跃

最后, pool_maxsize.

首先, 仅当你在多线程环境下使用session,你才应该关心 pool_maxsize , 例如,从多个线程使用同一个session发出并发请求.

实际上,pool_maxsize 是一个用来初始化urllib3's HTTPConnectionPool的参数, 它才是真正含义上的连接池. HTTPConnectionPool 是一个容器,针对于特定的host保存连接的集合, 且 pool_maxsize 是这个集合的最大值. 如果你运行你的代码在同一个线程,它是不可能针对多个主机创建多个连接的,因为 requests library 是阻塞的, 因此HTTP请求总是一个接着一个发送.

如果使用多线程就会不一样.

def thread_get(url):
s.get(url) s = requests.Session()
s.mount('https://', HTTPAdapter(pool_connections=1, pool_maxsize=2))
t1 = Thread(target=thread_get, args=('https://www.zhihu.com',))
t2 = Thread(target=thread_get, args=('https://www.zhihu.com/question/36612174',))
t1.start();t2.start()
t1.join();t2.join()
t3 = Thread(target=thread_get, args=('https://www.zhihu.com/question/39420364',))
t4 = Thread(target=thread_get, args=('https://www.zhihu.com/question/21362402',))
t3.start();t4.start()
"""output
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.zhihu.com
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (2): www.zhihu.com
DEBUG:requests.packages.urllib3.connectionpool:"GET /question/36612174 HTTP/1.1" 200 21906
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 200 2606
DEBUG:requests.packages.urllib3.connectionpool:"GET /question/21362402 HTTP/1.1" 200 57556
DEBUG:requests.packages.urllib3.connectionpool:"GET /question/39420364 HTTP/1.1" 200 28739
"""

看见了吗? 它为相同的host建立了俩个连接: www.zhihu.com, 就像我说的, 这个仅发生在多线程环境下. 在这个例子中,我们创建了一个连接池,使用了参数 pool_maxsize=2, 发现同一时间不会超过俩个连接,因此这是足够的,我们看到t3 和 t4 没有创建新的连接,他们重用了就的连接.

如果没有足够的大小呢?

s = requests.Session()
s.mount('https://', HTTPAdapter(pool_connections=1, pool_maxsize=1))
t1 = Thread(target=thread_get, args=('https://www.zhihu.com',))
t2 = Thread(target=thread_get, args=('https://www.zhihu.com/question/36612174',))
t1.start()
t2.start()
t1.join();t2.join()
t3 = Thread(target=thread_get, args=('https://www.zhihu.com/question/39420364',))
t4 = Thread(target=thread_get, args=('https://www.zhihu.com/question/21362402',))
t3.start();t4.start()
t3.join();t4.join()
"""output
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.zhihu.com
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (2): www.zhihu.com
DEBUG:requests.packages.urllib3.connectionpool:"GET /question/36612174 HTTP/1.1" 200 21906
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 200 2606
WARNING:requests.packages.urllib3.connectionpool:Connection pool is full, discarding connection: www.zhihu.com
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (3): www.zhihu.com
DEBUG:requests.packages.urllib3.connectionpool:"GET /question/39420364 HTTP/1.1" 200 28739
DEBUG:requests.packages.urllib3.connectionpool:"GET /question/21362402 HTTP/1.1" 200 57556
WARNING:requests.packages.urllib3.connectionpool:Connection pool is full, discarding connection: www.zhihu.com
"""

现在, pool_maxsize=1,会打印警告日志如下,就像下面的:

Connection pool is full, discarding connection: www.zhihu.com

可以注意到仅有一个连接被保存在连接池中, 为t3或者t4创建一个新的连接. 很明显这是不够的. 这就是为什么在 urllib3's 文档中说明如下:

如果你计划在连接池环境下使用一个连接池, 你应该设置连接池最大数量为一个比较高的值,例如和线程的数量相同.

最后, HTTPAdapter 实例映射到不同的前缀是相互独立的.

s = requests.Session()
s.mount('https://', HTTPAdapter(pool_connections=1, pool_maxsize=2))
s.mount('https://baidu.com', HTTPAdapter(pool_connections=1, pool_maxsize=1))
t1 = Thread(target=thread_get, args=('https://www.zhihu.com',))
t2 =Thread(target=thread_get, args=('https://www.zhihu.com/question/36612174',))
t1.start();t2.start()
t1.join();t2.join()
t3 = Thread(target=thread_get, args=('https://www.zhihu.com/question/39420364',))
t4 = Thread(target=thread_get, args=('https://www.zhihu.com/question/21362402',))
t3.start();t4.start()
t3.join();t4.join()
"""output
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): www.zhihu.com
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (2): www.zhihu.com
DEBUG:requests.packages.urllib3.connectionpool:"GET /question/36612174 HTTP/1.1" 200 21906
DEBUG:requests.packages.urllib3.connectionpool:"GET / HTTP/1.1" 200 2623
DEBUG:requests.packages.urllib3.connectionpool:"GET /question/39420364 HTTP/1.1" 200 28739
DEBUG:requests.packages.urllib3.connectionpool:"GET /question/21362402 HTTP/1.1" 200 57669
"""

上面的代码很容易,不需要再次解释了.

就这样了,希望这篇文章帮助你更好的理解requests库. 顺便说一下,我创建了一个gist here 这里包含这篇文章的测试代码. 随便下载并尝试吧

Appendix

  1. For https, requests uses urllib3's HTTPSConnectionPool, but it's pretty much the same as HTTPConnectionPool so I didn't differeniate them in this article.
  2. Session's mount method ensures the longest prefix gets matched first. Its implementation is pretty interesting so I posted it here.

    def mount(self, prefix, adapter):
    """Registers a connection adapter to a prefix.
    Adapters are sorted in descending order by key length."""
    self.adapters[prefix] = adapter
    keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
    for key in keys_to_move:
    self.adapters[key] = self.adapters.pop(key)

    Note that self.adapters is an OrderedDict.

【转载-译文】requests库连接池说明的更多相关文章

  1. python之requests urllib3 连接池

    0.目录 1.参考 2. pool_connections 默认值为10,一个站点主机host对应一个pool (4)分析 host A>>host B>>host A pag ...

  2. JDBC----数据库连接池(connection pool)

    •数据库连接池的基本思想就是为数据库连接建立一个"缓冲池".预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从"缓冲池"中取出一个,使用完毕之后再 ...

  3. [转载] 基于zookeeper、连接池、Failover/LoadBalance等改造Thrift 服务化

    转载自http://blog.csdn.net/zhu_tianwei/article/details/44115667 http://blog.csdn.net/column/details/sli ...

  4. DBUtils--数据库连接池

    介绍 DBUtils是一套Python数据库连接池包,并允许对非线程安全的数据库接口进行线程安全包装. pg大概是是PostgreSQL(基于PyGreSQL)数据库,DB是其他数据库 Steady[ ...

  5. Flask--数据库连接池

    目录 数据库连接池 pymsql链接数据库 数据库连接池版 数据库连接池 pymsql链接数据库 import pymysql conn = pymysql.connect(host='127.0.0 ...

  6. 【转载】requests库的7个主要方法、13个关键字参数以及响应对象的5种属性

    Python爬虫常用模块:requests库的7个主要方法.13个关键字参数以及响应对象的5种属性 原文链接: https://zhuanlan.zhihu.com/p/67489739

  7. c3p0-数据库连接池原理

    一直用c3p0很久了,但也没时间或没主动去研究过,直到最近频频在出现一些莫名其妙的问题,觉得还是有必要了解和研究一下. c3p0是什么 c3p0的出现,是为了大大提高应用程序和数据库之间访问效率的. ...

  8. python requests库学习笔记(上)

    尊重博客园原创精神,请勿转载! requests库官方使用手册地址:http://www.python-requests.org/en/master/:中文使用手册地址:http://cn.pytho ...

  9. 四个流行的Java连接池之Proxool篇

    Proxool是一个JavaSQL Driver驱动程序,提供了对你选择的其它类型的驱动程序的连接池封装.可以非常简单的移植到现存的代码中.完全可配置.快速,成熟,健壮.可以透明地为你现存的JDBC驱 ...

随机推荐

  1. SmartUpload文件上传组件的使用教程

    在项目中使用SmartUpload组件可以进行文件的上传和下载操作 使用步骤 1.导入jar包,进行build path操作 2.编写文件上传页面,代码如下 <form action=" ...

  2. ADB not responding

    1.如下错误: 2.执行 如下代码 : netstat -aon|findstr "5037" 3.打开任务管理器  找到PID 为 5536 的进程  将该进程关闭 4.重启 A ...

  3. Web开发之404小结

    404算是Web工程里最常见的错误代号了.今天做一个小结: 场景:[Tomcat运行正常,但无法访问自己建的项目:404] 结果:在URL拼写正确的情况下,无法访问目标工程任何页面 信息:[404]: ...

  4. django错误笔记(xadmin)——AttributeError: 'Settings' object has no attribute 'TEMPLATE_CONTEXT_PROCESSORS'

    使用Xadmin,执行makemigrations和migrate时运行报错提示: AttributeError: 'Settings' object has no attribute 'TEMPLA ...

  5. 修改mysql的用户root密码

    第一种方法:root用户登录系统/usr/local/mysql/bin/mysqladmin -u root -p password 新密码enter password 旧密码 第二种方法:root ...

  6. tidb 架构 ~Tidb学习系列(4)

    一 简介:今天我们继续学习tidb 二 集群管理 0 集群配置       验证 4台一组 3个kv 一个pd+server       上线 6台一组   1 动态添加kv服务       nohu ...

  7. mysql 查询优化~sql优化通用

    一 简介:今天我们来探讨下SQL语句的优化基础 二 基础规则: 一 通用: 1 避免索引字段使用函数     2 避免发生隐式转换     3 order by字段需要走索引,否则会发生filesor ...

  8. JavaScript学习 - 基础(一)

    ECMAscript ECMAscript是一个重要的标准,但它并不是JAVAscript唯一的部分,当然,也不是唯一标准化的部分,实际上,一个完整的JAVAscript实现是由一下3个不同的部分组成 ...

  9. python内置模块之unittest测试(五)

    系列文章 python模块分析之random(一) python模块分析之hashlib加密(二) python模块分析之typing(三) python模块分析之logging日志(四) pytho ...

  10. div里 datapicker显示异常的情况之一

    现象:datepicker控价显示一半 因为Div高度太小所以设置一个最小高度min-height这样就可以让时间控价显示完整了.