我如何优化这个 Asyncio 代码片段以在突发期间每秒发出更多请求?

How can I optimize this Asyncio slice of code to make more requests per second in a burst period?

所以这是我的小代码块。它只是一个向 Twilio 发送 10 post 请求的异步循环:

import time
import aiohttp
import asyncio


async def asynchronous():
    tasks = [f('NumberFrom', 'NumberTo', 'asyncio imo'),
             f('NumberFrom', 'NumberTo', 'asyncio imo'),
             f('NumberFrom', 'NumberTo', 'asyncio imo'),
             f('NumberFrom', 'NumberTo', 'asyncio imo'),
             f('NumberFrom', 'NumberTo', 'asyncio imo'),
             f('NumberFrom', 'NumberTo', 'asyncio imo'),
             f('NumberFrom', 'NumberTo', 'asyncio imo'),
             f('NumberFrom', 'NumberTo', 'asyncio imo'),
             f('NumberFrom', 'NumberTo', 'asyncio imo'),
             f('NumberFrom', 'NumberTo', 'asyncio imo')]
    await asyncio.gather(*tasks)


async def f(NumberFrom, NumberTo, MessageBody):
    try:
        print('Sent at %s' % time.time())
        async with aiohttp.ClientSession() as session:
            await session.post('https://api.twilio.com/2010-04-01/Accounts/AuthPass/Messages.json',
                                data={'From': NumberFrom, 'To': NumberTo, 'Body': MessageBody}, 
                                auth=aiohttp.BasicAuth(login='AuthUser', password='AuthPass'))
        print('Done at at %s' % time.time())
    except Exception as err:
        print('Error encountered at %s' % time.time())


asyncio.run(asynchronous())

在有人问之前,我有一个 Twilio 的付费帐户,不会免费加载或向他们发送垃圾邮件。我不是想用短信轰炸任何人。我只需要偶尔发送一连串的消息,并且每条消息都需要或多或少地同时发送到不同的号码。

目前,我正在使用线程模块执行此操作。我为每条消息启动一个单独的线程。这对于一些数字来说很好,但是当你需要打开多个线程时它会变得低效。每次执行此操作时,我都必须打开 20 个线程,并且我正在寻找一种比线程更有效的异步发送 20 post 请求的方法。

这是我现在使用 asyncio 获得的性能:

>>> asyncio.run(asynchronous())
0.0
Sent at 1553142004.4640338
Sent at 1553142004.5059218
Sent at 1553142004.5119061
Sent at 1553142004.5178897
Sent at 1553142004.5238738
Sent at 1553142004.5288606
Sent at 1553142004.5348446
Sent at 1553142004.5388453
Sent at 1553142004.5448182
Sent at 1553142004.5488071
Done at 1553142004.9834092
Done at 1553142004.9913745
Done at 1553142005.0013483
Done at 1553142005.0153105
Done at 1553142005.0264556
Done at 1553142005.0342588
Done at 1553142005.0472543
Done at 1553142005.0581958
Done at 1553142005.066205
Done at 1553142005.0731542
>>> 

我平均每秒约有 100 个 post 请求。出于某种原因,我认为 asyncio 会比这更快。我读过 Python 每秒能够处理 1,000,000 个请求的文章。我没想到会这样,我只是觉得我可以从 asyncio 中获得更多的性能。

我的代码中是否存在明显的错误,导致使用 asyncio 或其他方式降低效率?或者这只是 asyncio 可以做到的顶峰?我不是 Python 的新手,但我是 asyncio 的新手,所以我不知道我在这里做什么。请解释任何显而易见的事情。

作为参考,我是 运行 4 核 3.2GHz 英特尔 i7 处理器,这个脚本是当时唯一的东西 运行。我知道我的 CPU 不是瓶颈。

我的互联网在 运行 时达到约 250Kbps,但这远不及我的 ISP 上限 3.5Mbps。我知道我的网络不是瓶颈。

我是运行这个脚本在Python3.7.2中的IDLEshell.

您应该将自定义连接器传递给会话:

connector = aiohttp.TCPConnector(limit=None)
async with aiohttp.ClientSession(connector=connector) as session:
    # ...

解释的原因 更详细。


另请注意,article 提出百万请求并不能保证 "per second"。

您可能将它与 article about handling requests on server-side using Japronto which is something completely different (not mentioning this article has its own issues) 混淆了。


更新:

总是有与准备请求相关的开销。您可以尝试使用单会话来节省时间:

import time
import aiohttp
import asyncio


async def asynchronous():
    async with aiohttp.ClientSession() as session:
        tasks = [f('NumberFrom', 'NumberTo', 'asyncio imo', session),
                 f('NumberFrom', 'NumberTo', 'asyncio imo', session),
                 f('NumberFrom', 'NumberTo', 'asyncio imo', session),
                 f('NumberFrom', 'NumberTo', 'asyncio imo', session),
                 f('NumberFrom', 'NumberTo', 'asyncio imo', session),
                 f('NumberFrom', 'NumberTo', 'asyncio imo', session),
                 f('NumberFrom', 'NumberTo', 'asyncio imo', session),
                 f('NumberFrom', 'NumberTo', 'asyncio imo', session),
                 f('NumberFrom', 'NumberTo', 'asyncio imo', session),
                 f('NumberFrom', 'NumberTo', 'asyncio imo', session)]
        await asyncio.gather(*tasks)


async def f(NumberFrom, NumberTo, MessageBody, session):
    try:
        print('Sent at %s' % time.time())
        await session.get('http://httpbin.org/delay/1')
        print('Done at at %s' % time.time())
    except Exception as err:
        print('Error encountered at %s' % time.time())


asyncio.run(asynchronous())