webdevqa.jp.net

requests.adapters.HTTPAdapterのpool_connectionsの意味は何ですか?

リクエストのSessionを初期化すると、2つの HTTPAdapter が作成され、 httphttpsにマウント

HTTPAdapterの定義は次のとおりです。

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

pool_maxsize(プールが保存できるセッションの数)の意味は理解していますが、pool_connectionsの意味や機能は理解していません。 Docは言う:

Parameters: 
pool_connections – The number of urllib3 connection pools to cache.

しかし、「キャッシュする」とはどういう意味ですか?そして、複数の接続プールを使用するポイントは何ですか?

15
laike9m

Requestsは rllib を使用して、接続やその他の機能を管理します。

接続の再利用は、繰り返し発生するHTTPリクエストのパフォーマンスを維持する上で重要な要素です。 rllib3 README説明

接続を再利用するのはなぜですか?

パフォーマンス。通常urllib呼び出しを行う場合、要求ごとに個別のソケット接続が作成されます。既存のソケット(HTTP 1.1以降でサポート)を再利用することにより、要求はサーバー側で使用するリソースが少なくなり、クライアント側での応答時間が短縮されます。 [...]

あなたの質問に答えるために、「pool_maxsize」はホストごとに維持する接続の数です(これはマルチスレッドアプリケーションに役立ちます)が、「pool_connections」は維持するホストプールの数です。たとえば、100の異なるホストに接続している場合、およびpool_connections=10の場合、最新の10個のホストの接続のみが再利用されます。

11
shazow

これについて 記事 を書きました。ここに貼り付けました:

リクエストの秘密:pool_connectionsとpool_maxsize

Requests は、最もよく知られているとは言わないまでも、Python Pythonプログラマー向けのサードパーティライブラリです。 APIと高性能のため、HTTPリクエストには標準ライブラリが提供するurllib2の代わりにリクエストを使用する傾向がありますが、毎日リクエストを使用する人は内部を知らない可能性があるため、今日はそのうちの2つを紹介します:_pool_connections_および_pool_maxsize_。

Sessionから始めましょう:

_import requests

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

とても簡単です。リクエストのSessionがCookieを永続化できることはご存知でしょう。涼しい。しかし、Sessionmount メソッドがあることを知っていますか?

mount(prefix, adapter)
接続アダプタをプレフィックスに登録します。
アダプターは、キーの長さの降順でソートされます。

番号?ええと、実際、あなたは Sessionオブジェクトを初期化する :のときにすでにこのメソッドを使用しています。

_class Session(SessionRedirectMixin):

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

ここで興味深い部分があります。 Ian Cordascoの記事 Retries in Requests を読んだ場合は、HTTPAdapterを使用して再試行機能を提供できることを知っておく必要があります。しかし、実際にはHTTPAdapterとは何ですか? doc からの引用:

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

Urllib3用の組み込みHTTPアダプター。

トランスポートアダプタインターフェイスを実装することにより、リクエストセッションがHTTPおよびHTTPSURLに接続するための一般的なインターフェイスを提供します。このクラスは通常、内部のSessionクラスによって作成されます。

パラメーター:
* _pool_connections_ –キャッシュするurllib3接続プールの数。 * _pool_maxsize_ –プールに保存する接続の最大数。 * max_retries(int) –各接続が試行する必要のある再試行の最大数。これは、失敗したDNSルックアップ、ソケット接続、および接続タイムアウトにのみ適用され、データがサーバーに到達した要求には適用されないことに注意してください。デフォルトでは、リクエストは失敗した接続を再試行しません。リクエストを再試行する条件をきめ細かく制御する必要がある場合は、urllib3のRetryクラスをインポートして、代わりにそれを渡します。 * _pool_block_ –接続プールが接続をブロックする必要があるかどうか。使用法:

_>>> import requests
>>> s = requests.Session()
>>> a = requests.adapters.HTTPAdapter(max_retries=3)
>>> s.mount('http://', a)
_

上記のドキュメントで混乱する場合は、ここに私の説明があります。HTTPアダプターが行うことは、単にターゲットURL に従って異なるリクエストに異なる構成を提供することです。上記のコードを覚えていますか?

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

デフォルトの引数_pool_connections=10, pool_maxsize=10, max_retries=0, pool_block=False_で2つのHTTPAdapterオブジェクトを作成し、それぞれ_https://_と_http://_にマウントします。これは、最初のHTTPAdapter()の構成が_http://xxx_にリクエストを送信しようとした場合に使用され、2番目のHTTPAdapter()は_https://xxx_へのリクエストに使用されます。この場合、2つの構成は同じですが、httphttpsへのリクエストは引き続き別々に処理されます。それが何を意味するのかは後でわかります。

私が言ったように、この記事の主な目的は_pool_connections_と_pool_maxsize_を説明することです。

まず、_pool_connections_を見てみましょう。昨日私はstackoverflowで 質問 を提起しました。私の理解が正しいかどうかわからないので、答えは私の不確実性を排除します。 HTTPは、ご存知のとおり、TCPプロトコルに基づいています。HTTP接続もTCP接続であり、[//のタプルによって識別されます。 //] 5 /(+)/値:

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

_www.example.com_とのHTTP/TCP接続を確立したとすると、サーバーが_Keep-Alive_をサポートしていると仮定します。次に、_www.example.com/a_または_www.example.com/b_にリクエストを送信するときは、同じ接続では、5つの値のいずれも変更されません。実際、 リクエストのセッションは自動的にこれを行います そして可能な限り接続を再利用します。

問題は、古い接続を再利用できるかどうかを決定するものは何ですか?はい、_pool_connections_!

pool_connections –キャッシュするurllib3接続プールの数。

あまり多くの用語を持ち込みたくないのはわかっています。これが最後の用語です。約束します。簡単に理解できるように、 1つの接続プールは1つのホストに対応します。

次に例を示します(無関係な行は無視されます)。

_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://_にマウントされます。これは、一度に1つの接続プールのみが存続することを意味します。 s.get('https://www.baidu.com')を呼び出した後、キャッシュされた接続プールはconnectionpool('https://www.baidu.com')になります。 s.get('https://www.zhihu.com')が来ましたが、セッションは、同じホストではないため、以前にキャッシュされた接続を使用できないことを検出しました(1つの接続プールは1つのホストに対応します、覚えていますか?)。したがって、セッションは新しい接続プール、または必要に応じて接続を作成する必要がありました。 _pool_connections=1_であるため、セッションは2つの接続プールを同時に保持できません。したがって、セッションはconnectionpool('https://www.baidu.com')である古い接続プールを破棄し、connectionpool('https://www.zhihu.com')である新しい接続プールを保持しました。次のgetは同じです。これが、ロギングに3つの_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
"""
_

これで、接続を2回だけ作成し、1つの接続確立時間を節約できました。

最後に、_pool_maxsize_。

何よりもまず、 multithreaded 環境でSessionを使用する場合にのみ、_pool_maxsize_に注意する必要があります。たとえば、 multithreaded 環境で同じSession

実際、_pool_maxsize_は、urllib3の HTTPConnectionPool を初期化するための引数です。これは、まさに上記の接続プールです。 HTTPConnectionPoolは特定のホストへの接続のコレクションのコンテナーであり、_pool_maxsize_は再利用できる保存する接続の数です。 1つのスレッドでコードを実行している場合、同じホストへの複数の接続を作成することは不可能であるか、必要ではありません。リクエストライブラリがブロックされているため、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
"""
_

見る?私が言ったように、それは同じホスト_www.zhihu.com_に対して2つの接続を確立しました。これはマルチスレッド環境でのみ発生する可能性があります。この場合、_pool_maxsize=2_を使用して接続プールを作成します。同時に接続する接続は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
_

また、このプールに保存できる接続は1つだけであるため、_t3_または_t4_に対して新しい接続が再度作成されることにも注意してください。明らかに、これは非常に非効率的です。そのため、urllib3のドキュメントには次のように記載されています。

マルチスレッド環境でこのようなプールを使用することを計画している場合は、プールの最大サイズをスレッド数などのより大きな数に設定する必要があります。

最後になりましたが、異なるプレフィックスにマウントされた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
"""
_

上記のコードはわかりやすいので説明しません。

それだけだと思います。この記事がリクエストをよりよく理解するのに役立つことを願っています。ところで、私は要点を作成しました ここ この記事で使用されているすべてのテストコードが含まれています。ダウンロードして遊んでください:)

付録

  1. Httpsの場合、リクエストはurllib3の HTTPSConnectionPool を使用しますが、HTTPConnectionPoolとほとんど同じなので、この記事では区別しません。
  2. Sessionmountメソッドは、最長のプレフィックスが最初に一致することを保証します。その実装はかなり興味深いので、ここに投稿しました。

    _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)
    _

    _self.adapters_はOrderedDictであることに注意してください。

31
laike9m

既存のQ&Aと記事を提供してくれた@ laike9mに感謝しますが、既存の回答では、_pool_maxsize_の微妙さとマルチスレッドコードとの関係について言及していません。

概要

  • _pool_connections_は、1つの(ホスト、ポート、スキーム)エンドポイントから特定の時間にプール内で存続できる接続の数です。 nで再利用するためにプール内の最大Sessionopen TCP接続を維持したい場合は、_pool_connections=n_が必要です。
  • _pool_maxsize_のデフォルト値(_pool_block_内)がrequestsではなくFalseであるため、_requests.adapters.HTTPAdapter_はTrueのユーザーには事実上無関係です。

詳細

ここで正しく指摘されているように、_pool_connections_は、アダプターのプレフィックスを指定して開いている接続の最大数です。それは例を通して最もよく説明されます:

_>>> import requests
>>> from requests.adapters import HTTPAdapter
>>> 
>>> from urllib3 import add_stderr_logger
>>> 
>>> add_stderr_logger()  # Turn on requests.packages.urllib3 logging
2018-12-21 20:44:03,979 DEBUG Added a stderr logging handler to logger: urllib3
<StreamHandler <stderr> (NOTSET)>
>>> 
>>> s = requests.Session()
>>> s.mount('https://', HTTPAdapter(pool_connections=1))
>>> 
>>> # 4 consecutive requests to (github.com, 443, https)
... # A new HTTPS (TCP) connection will be established only on the first conn.
... s.get('https://github.com/requests/requests/blob/master/requests/adapters.py')
2018-12-21 20:44:03,982 DEBUG Starting new HTTPS connection (1): github.com:443
2018-12-21 20:44:04,381 DEBUG https://github.com:443 "GET /requests/requests/blob/master/requests/adapters.py HTTP/1.1" 200 None
<Response [200]>
>>> s.get('https://github.com/requests/requests/blob/master/requests/packages.py')
2018-12-21 20:44:04,548 DEBUG https://github.com:443 "GET /requests/requests/blob/master/requests/packages.py HTTP/1.1" 200 None
<Response [200]>
>>> s.get('https://github.com/urllib3/urllib3/blob/master/src/urllib3/__init__.py')
2018-12-21 20:44:04,881 DEBUG https://github.com:443 "GET /urllib3/urllib3/blob/master/src/urllib3/__init__.py HTTP/1.1" 200 None
<Response [200]>
>>> s.get('https://github.com/python/cpython/blob/master/Lib/logging/__init__.py')
2018-12-21 20:44:06,533 DEBUG https://github.com:443 "GET /python/cpython/blob/master/Lib/logging/__init__.py HTTP/1.1" 200 None
<Response [200]>
_

上記では、接続の最大数は1です。 _(github.com, 443, https)_です。新しい(ホスト、ポート、スキーム)トリプルからリソースを要求する場合、Sessionは内部的に既存の接続をダンプして、新しい接続用のスペースを作ります。

_>>> s.get('https://www.rfc-editor.org/info/rfc4045')
2018-12-21 20:46:11,340 DEBUG Starting new HTTPS connection (1): www.rfc-editor.org:443
2018-12-21 20:46:12,185 DEBUG https://www.rfc-editor.org:443 "GET /info/rfc4045 HTTP/1.1" 200 6707
<Response [200]>
>>> s.get('https://www.rfc-editor.org/info/rfc4046')
2018-12-21 20:46:12,667 DEBUG https://www.rfc-editor.org:443 "GET /info/rfc4046 HTTP/1.1" 200 6862
<Response [200]>
>>> s.get('https://www.rfc-editor.org/info/rfc4047')
2018-12-21 20:46:13,837 DEBUG https://www.rfc-editor.org:443 "GET /info/rfc4047 HTTP/1.1" 200 6762
<Response [200]>
_

数を_pool_connections=2_に増やしてから、3つの一意のホストの組み合わせを切り替えると、同じことが再生されます。 (もう1つ注意すべき点は、セッションが同じ方法でCookieを保持して送り返すことです。)

_pool_maxsize_については、_urllib3.poolmanager.PoolManager_に渡され、最終的には_urllib3.connectionpool.HTTPSConnectionPool_に渡されます。 maxsizeのdocstringは次のとおりです。

再利用できる保存する接続の数。マルチスレッドの状況では、複数が役立ちます。 blockがFalseに設定されている場合、さらに多くの接続が作成されますが、一度使用されると保存されません。

ちなみに、HTTPAdapterのデフォルトはTrueですが、_block=False_はHTTPConnectionPoolのデフォルトです。これは、_pool_maxsize_がHTTPAdapterにほとんどまたはまったく影響を与えないことを意味します。

さらに、requests.Session()notスレッドセーフです。複数のスレッドから同じsessionインスタンスを使用しないでください。 ( ここ および ここ を参照してください。)本当に必要な場合は、各スレッドに独自のローカライズされたセッションインスタンスを貸し、そのセッションを使用して作成するのがより安全な方法です。 threading.local() を介した複数のURLを介したリクエスト:

_import threading
import requests

local = threading.local()  # values will be different for separate threads.

vars(local)  # initially empty; a blank class with no attrs.


def get_or_make_session(**adapter_kwargs):
    # `local` will effectively vary based on the thread that is calling it
    print('get_or_make_session() called from id:', threading.get_ident())

    if not hasattr(local, 'session'):
        session = requests.Session()
        adapter = requests.adapters.HTTPAdapter(**kwargs)
        session.mount('http://', adapter)
        session.mount('https://', adapter)
        local.session = session
    return local.session
_
1
Brad Solomon