Python 3.x 使用asyncio和aiohttp的异步程序的最佳体系结构

Python 3.x 使用asyncio和aiohttp的异步程序的最佳体系结构,python-3.x,python-asyncio,aiohttp,Python 3.x,Python Asyncio,Aiohttp,我正试图了解如何最好地构建一个计划,并做到以下几点: 考虑多重分析。每个分析都从多个数据源(RESTAPI)请求数据。在每次分析中,当从数据源收集所有数据时,将检查数据是否存在一个或多个条件。如果满足这些条件,将向另一个数据源发出另一个请求 目标是为所有异步分析收集数据,检查每个分析的条件,请求是否满足条件,然后重复。因此,需要满足以下要求: 在特定分析中收集所有数据后,而不是在所有分析中收集数据后,检查数据的条件 如果满足条件,则首先提出请求,而不是在检查所有分析的条件之后 get data-

我正试图了解如何最好地构建一个计划,并做到以下几点:

考虑多重分析。每个分析都从多个数据源(RESTAPI)请求数据。在每次分析中,当从数据源收集所有数据时,将检查数据是否存在一个或多个条件。如果满足这些条件,将向另一个数据源发出另一个请求

目标是为所有异步分析收集数据,检查每个分析的条件,请求是否满足条件,然后重复。因此,需要满足以下要求:

  • 在特定分析中收集所有数据后,而不是在所有分析中收集数据后,检查数据的条件
  • 如果满足条件,则首先提出请求,而不是在检查所有分析的条件之后
  • get data->check for conditions->maybe request something循环计划每X分钟或每X小时运行一次
  • 我制作了以下演示:

    import asyncio
    import random
    
    
    async def get_data(list_of_data_calls):
        tasks = []
        for l in list_of_data_calls:
            tasks.append(asyncio.ensure_future(custom_sleep(l)))
        return await asyncio.gather(*tasks)
    
    
    async def custom_sleep(time):
        await asyncio.sleep(time)
        return random.randint(0, 100)
    
    
    async def analysis1_wrapper():
        while True:
            print("Getting data for analysis 1")
            res = await get_data([5, 3])
            print("Data collected for analysis 1")
            for integer in res:
                if integer > 80:
                    print("Condition analysis 1 met")
                else:
                    print("Condition analysis 1 not met")
            await asyncio.sleep(10)
    
    
    async def analysis2_wrapper():
        while True:
            print("Getting data for analysis 2")
            res = await get_data([5, 3])
            print("Data collected for analysis 2")
            for integer in res:
                if integer > 50:
                    print("Condition analysis 2 met")
                else:
                    print("Condition analysis 2 not met")
            await asyncio.sleep(10)
    
    
    loop = asyncio.get_event_loop()
    tasks = analysis1_wrapper(), analysis2_wrapper()
    loop.run_until_complete(asyncio.gather(*tasks))
    loop.close()
    
    这将产生以下输出:

    Getting data for analysis 2
    Getting data for analysis 1
    Data collected for analysis 2
    Condition analysis 2 not met
    Condition analysis 2 not met
    Data collected for analysis 1
    Condition analysis 1 not met
    Condition analysis 1 not met
    Getting data for analysis 2
    Getting data for analysis 1
    Data collected for analysis 2
    Condition analysis 2 met
    Condition analysis 2 not met
    Data collected for analysis 1
    Condition analysis 1 not met
    Condition analysis 1 not met
    Getting data for analysis 2
    Getting data for analysis 1
    Data collected for analysis 2
    Condition analysis 2 not met
    Condition analysis 2 not met
    Data collected for analysis 1
    Condition analysis 1 not met
    Condition analysis 1 not met
    

    这似乎是我想要的。然而,由于我在asyncio和aiohttp方面的经验有限,我不确定这是否是一种好的方法。我希望将来能够在管道中添加步骤,例如,如果满足条件,根据请求的逻辑做一些事情。而且,它应该可以扩展到许多分析,而不会损失太多的速度。

    是的,基本上就是这样。需要考虑的几件事:

  • 并发限制
  • 虽然您可能有无限数量的并发任务,但随着并发性的增加,每个任务的响应时间也会增加,吞吐量会在某个点停止增长,甚至降低。因为只有一个主线程来执行所有操作,所以当回调太多时,即使网络响应在几毫秒前到达,回调也必须排队。为了平衡这一点,您通常需要控制性能的最大并发性

  • CPU密集型操作
  • 您的代码没有显示,但担心的是条件检查可能会占用大量CPU。在这种情况下,您应该将任务延迟到一个(无GIL问题)或子流程(如果GIL是问题)中,原因有二:1。停止阻塞的主线程,以免损害并发性。2.更有效地使用多个CPU

  • 任务控制

  • 对于每次分析,您当前的代码在循环中休眠10秒。这使得很难优雅地关闭分析仪,更不用说动态缩放了。理想的模式应该是这样的,在这种模式中,您可以将具有某种控制的任务生成到一个队列中,然后一群工作人员从队列中检索任务并同时处理它们。

    回答得好!谢谢几个问题:关于1)我知道在某个时候,回调正在排队。我想当轮到他们的时候,回调就会被执行。据我所知,您是说太多的回调实际上会降低总体性能,而不仅仅是创建一个回调队列。为什么呢?关于2)将任务发送到线程池或子进程之间有什么区别?关于3)10秒钟的睡眠就是一个例子。但我会的。为此,研究生产者-消费者模式。使用某种调度器怎么样?当然!1) 根据事件循环,它用于维护延迟回调,它的时间复杂性为
    O(logn)
    heappop()
    。对于I/O轮询,它在不同平台上使用最具可扩展性的实现,例如在Linux上,它还需要
    O(logn)
    来更改轮询列表
    O(logn)
    很小,但需要一点时间。仍然是1),这实际上不是要担心的
    O(logn)
    ,而是不断增长的响应时间。因为在固定的物理机器上,最大吞吐量是固定的。因此,可以根据每次分析中的I/O时间比来计算不影响响应时间的最大并发性。例如,获取数据需要10秒,条件检查只需0.1秒,那么当CPU忙于检查1%任务的结果时,您可以有更多的并发任务等待数据。一旦超过这个值,东西就会在错误的位置排队,这使得管理变得困难。2)线程共享相同的内容,而子进程则不共享。因此,如果任务是纯Python编写的(没有C扩展),那么它们可能会使用多达一个CPU内核和任意数量的线程,而对于进程,它们可能会使用所有CPU内核。3)是的,定制调度程序绝对是好的。如果任务是集中调度的(而不是在每个任务运行程序中调度),则需要处理协同路由之间的通信。在这种情况下,可能是一个错误@mfvas