了解 aiohttp.TCPConnector 池和连接限制

Understanding aiohttp.TCPConnector pooling & connection limits

我正在试验 limitlimit_per_host 参数到 aiohttp.connector.TCPConnector

在下面的脚本中,我将 connector = aiohttp.connector.TCPConnector(limit=25, limit_per_host=5) 传递给 aiohttp.ClientSession,然后向 docs.aiohttp.org 打开 2 个请求,向 github.com 打开 3 个请求。

session.request 的结果是 aiohttp.ClientResponse 的一个实例,在这个例子中我有意不通过 .close() 或 [= 对其调用 .close() 22=]。我假设这将使连接池保持打开状态,并将到该(主机、ssl、端口)的可用连接减少三倍 -1。

下面的table表示每次请求后的._available_connections()为什么即使在完成对 docs.aiohttp.org 的第二个请求后数字仍停留在 4? 这两个连接可能仍然打开并且尚未访问 ._content 或者被关闭。可用连接数不应该减少 1 吗?

After Request Num.        To                    _available_connections
1                         docs.aiohttp.org      4
2                         docs.aiohttp.org      4   <--- Why?
3                         github.com            4
4                         github.com            3
5                         github.com            2

此外,为什么 ._acquired_per_host 只包含 1 个键? 我想我可能正在理解 TCPConnector 的方法;如何解释上述行为?

完整脚本:

import aiohttp


async def main():
    connector = aiohttp.connector.TCPConnector(limit=25, limit_per_host=5)

    print("Connector arguments:")
    print("_limit:", connector._limit)
    print("_limit_per_host:", connector._limit_per_host)
    print("-" * 70, end="\n\n")

    async with aiohttp.client.ClientSession(
        connector=connector,
        headers={"User-Agent": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36"},
        raise_for_status=True
    ) as session:

        # Make 2 connections to docs.aiohttp.org and 
        #      3 connections to github.com
        #
        # Note that these instances intentionally do not use
        # .close(), either explicitly or via __aexit__
        # in an async with block

        r1 = await session.request(
            "GET",
            "https://docs.aiohttp.org/en/stable/client_reference.html#connectors"
        )
        print_connector_attrs("r1", session)

        r2 = await session.request(
            "GET",
            "https://docs.aiohttp.org/en/stable/index.html"
        )
        print_connector_attrs("r2", session)

        r3 = await session.request(
            "GET",
            "https://github.com/aio-libs/aiohttp/blob/master/aiohttp/client.py"
        )
        print_connector_attrs("r3", session)

        r4 = await session.request(
            "GET",
            "https://github.com/python/cpython/blob/3.7/Lib/typing.py"
        )
        print_connector_attrs("r4", session)

        r5 = await session.request(
            "GET",
            "https://github.com/aio-libs/aiohttp"
        )
        print_connector_attrs("r5", session)


def print_connector_attrs(name: str, session: aiohttp.client.ClientSession):
    print("Connection attributes for", name, end="\n\n")
    conn = session._connector
    print("_conns:", conn._conns, end="\n\n")
    print("_acquired:", conn._acquired, end="\n\n")
    print("_acquired_per_host:", conn._acquired_per_host, end="\n\n")
    print("_available_connections:")
    for k in conn._acquired_per_host:
        print("\t", k, conn._available_connections(k))
    print("-" * 70, end="\n\n")


if __name__ == "__main__":
    import asyncio
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

输出粘贴在 https://pastebin.com/rvfzMTe3。我把它放在那里而不是放在这里,因为行很长而且不是很容易换行。

要解决您的主要问题“为什么即使在完成对 docs.aiohttp.org 的第二次请求后,号码仍停留在 4?”, 当调用 aiohttp.connector.BaseConnector._release() 时,连接计数将减少,如果您要在 session.request() 上使用 async with,则会调用此方法 或显式调用 .close() 或在您使用 .read() 阅读响应内容后。或者在 docs.aiohttp.org 请求的情况下,当服务器发送 EOF 时(当服务器不等待您流式传输响应内容而是将其全部发送以响应第一个请求时,就会发生这种情况)。这就是这里正在发生的事情。当您在 aiohttp.connector.BaseConnector._release() 中放置一个断点并检查堆栈时,您可以自己看到,您会看到调用了 aiohttp.http_parser.DeflateBuffer.feed_eof()aiohttp.streams.StreamReade._eof_callbacks 包含正在调用 self._connection.release()

aiohttp.client_reqrep.ClientResponse._response_eof()

至于你的第二个问题“为什么 ._acquired_per_host 只包含 1 个密钥”,这很奇怪。但是我在文档中什么也看不到。这是一个私有属性,所以我们不应该弄乱它。它可能只是命名错误的私有属性。