代码之家  ›  专栏  ›  技术社区  ›  jbuddy_13

AWS Lambda:异步导入和数据查询可减少延迟

  •  1
  • jbuddy_13  · 技术社区  · 2 年前

    假设我有这个Lambda函数,它需要做三件事:1/导入一些“重模块”(例如Pandas、Numpy),2/请求一些不重要的数据量,以及3/对这些数据进行一些分析。

    我认为一个合理的解决方案是定义三个异步函数, heavy_import_handler , query analyze . Importing modules in global scope via functions is a high-interest Q/A.

    因此,我应该能够启动查询,在等待响应时释放CPA,开始heavy_inmport_handler,并进行块分析,直到前两个功能完成。

    这是反模式吗?有没有更简单的方法?

    或者这可能是Lambda的标准解决方案,其中大量导入将在执行结束时从内存中释放?

    (额外的好处是:提供的并发性会让这些导入在内存中保持“热”状态吗?还是只是缓存执行环境以降低延迟?)

    1 回复  |  直到 2 年前
        1
  •  1
  •   Quassnoi    2 年前

    因此,我应该能够启动查询,在等待响应时释放CPA,开始heavy_inmport_handler,并进行块分析,直到前两个功能完成。

    我对Python中的异步IO不是很熟悉,但我仍然会发布它,因为它很可能也适用于Python。

    Here 你可以找到一张Lambda的生命周期图(我不确定是否允许我在这里复制它)。

    您正在为此图的INIT、INVOKE和SHUTDOWN块的持续时间付费。

    这些块由Lambda运行时的同步挂钩调用。它们可以生成异步线程/任务/回调/运行时提供的任何其他内容,但一旦同步部分返回,每个块的调用就会结束。

    您可以将数据和对资源的引用存储在容器的全局内存中,全局内存是在处理程序范围之外定义的变量。这是一种受支持甚至推荐的存储对共享资源的引用的方法,这些资源在每次调用时创建起来都很昂贵,例如数据库连接、文件句柄,在您的情况下还有繁重的模块。

    其中有一个棘手的部分。Lambda容器在一个名为 Firecracker 。该系统除其他外,可确保在Lambdas未积极运行时不会浪费AWS资源。为此,当Lambda容器不处于其生命周期的活动阶段时(即,当没有任何块在活动执行时),Firecracker会冻结它们。在用虚线标记的时间段内,Lambda中没有任何运行:没有异步代码,没有回调,什么都没有。它被冻住了。

    我熟悉的所有Lambda语言(即JavaScript、C#和Java)中的异步调用都是作为回调实现的,运行时会调用这些回调来响应一些外部事件(epoll/kqueue/io_uring、同步原语等)。

    当您的Lambda被冻结时,运行时也被冻结。即使条件适合,它也不会发出这些回调。您的异步代码不会在计费期之外运行。

    让我们看看这段Javascript代码:

    // INIT block starts
    const modulePromise = (async () => import("module"))();
    // INIT block completes. During INIT, handler is defined but not executed.
    
    export const handler = async (event) => {
        // INVOKE block starts
        const [module, data] = await Promise.all([modulePromise, queryData(event)]);
        await module.analyze(data);
        // INVOKE block completes
    }
    

    在正常运行时(如机器上带有Lambda运行时的Docker容器)中,调用 import 将启动一系列在后台运行的回调,最终实现返回的承诺 进口 。如果你打电话 handler 初始化容器几秒钟后,调用它时 modulePromise 可能已经解决,您的处理程序只需等待 query .

    在Firecracker环境中,同步部分返回的那一刻(即几乎立即)执行就被冻结。import可能(甚至可能不)通过(最终)调用来启动从文件系统读取第一个数据块 epoll 或者您的节点实例在后台使用的任何系统调用,但当数据从磁盘到达时,将没有活动的节点实例可以读取它。只有当您的执行环境解冻时,读取才会继续,所以到 处理程序 首先被调用, 模块承诺 总之,问题还没有解决。

    好消息是,在第一次调用处理程序时,模块导入仍将与数据提取并行。导入只发生一次,模块代码将保留在容器的全局内存中。

    一些(潜在的)坏消息是,初始化和第一次调用之间可能会经过很多时间,有时是几分钟。这对于文件系统读取可能并不重要,但对于一些资源(TCP连接、预签名的AWS请求等)来说可能已经足够过时了。你真的无法控制它。

    或者这可能是Lambda的标准解决方案,其中大量导入将在执行结束时从内存中释放?

    导入(以及全局容器数据中的任何内容)只会在容器生命周期结束时(最多几个小时和数万次处理程序调用)从内存中释放。

    在重负载下,Lambda实际上预先分配容器(创建容器并提前运行INIT代码)。当然,你会为此付费的。如果是这样的话,如果你更担心的是用户的体验,那么你实际上会更喜欢做常规的同步导入。它们将在处理程序的第一次调用(“冷启动”)发生时完成,因此实际上会更快。

    (额外的好处是:提供的并发性会让这些导入在内存中保持“热”状态吗?还是只是缓存执行环境以降低延迟?)

    同时执行的Lambdas在多个容器中执行。在单个容器中,执行是严格按顺序执行的。容器不共享任何内存。两个不同的容器将导入模块两次(并向您收取两次费用)。