【问题标题】:How do I run code that accesses an Apache OpenWhisk action's in-memory state after the action completes?在操作完成后,如何运行访问 Apache OpenWhisk 操作的内存状态的代码?
【发布时间】:2020-09-04 19:51:07
【问题描述】:

我正在测试使用 IBM Cloud Functions(托管 Apache OpenWhisk)在操作完成后在后台运行代码,但我在调用 setTimeout 时提供的回调没有在正确的时间运行,而且它从未运行除非我第二次调用该函数。它在那个时候(晚)运行。

详情:

我想到了两个用例:

  • 在内存中累积多个请求的数据,然后在大量累积或经过一定时间而没有请求时将大对象放入 Cloud Object Storage 存储桶。
  • 为 API 管理每个容器的数据库连接,以便我可以关闭 IBM Cloud Functions 尚未终止的未使用容器中的连接。

我认为这会起作用,因为我使用了其他平台,例如 Google Cloud Run,我注意到在后台运行代码(使用 setTimeout 等),在请求完成后在 Stackdriver 中看到此代码的日志。而且,甚至还有一个由 AWS 开发倡导者创建的完整库,用于在 AWS Lambda 的后台管理 MySQL 连接 (https://www.npmjs.com/package/serverless-mysql)。

我测试了以下函数:

// from https://stackoverflow.com/questions/105034/how-to-create-guid-uuid
function uuidv4() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(16);
    });
}

function main() {
    const runId = uuidv4().slice(31, 36);

    console.log(`function started (runId = ${runId})`);

    setTimeout(() => {
        console.log(`after 5s delay (runId = ${runId})`);
    }, 5000);

    return {
        msg: `ok (runId = ${runId})`,
    };
}

我使用命令ibmcloud fn action update logging-in-background src/index.js --kind nodejs:10 部署它。

我创建了一个 LogDNA 实例并将其设置为我的平台实例,以便我的函数日志会转到它。这是我在使用命令ibmcloud fn action invoke logging-in-background --blocking 调用函数三次后在日志中看到的内容,每次间隔 10 秒(CRN 已编辑):

May 18 17:26:23 functions REDACTED 2020-05-18T21:26:23.956013Z    stdout: function started (runId = 9be7c)
May 18 17:26:23 functions REDACTED Activation record '3589870e8ce44cc089870e8ce4acc018' for entity 'logging-in-background'
May 18 17:26:34 functions REDACTED 2020-05-18T21:26:34.111745Z    stdout: after 5s delay (runId = 9be7c)
May 18 17:26:34 functions REDACTED 2020-05-18T21:26:34.115043Z    stdout: function started (runId = faba6)
May 18 17:26:34 functions REDACTED Activation record 'ac47c067177648f187c0671776b8f1c2' for entity 'logging-in-background'
May 18 17:26:44 functions REDACTED 2020-05-18T21:26:44.248470Z    stdout: after 5s delay (runId = faba6)
May 18 17:26:44 functions REDACTED 2020-05-18T21:26:44.253822Z    stdout: function started (runId = 0af34)
May 18 17:26:44 functions REDACTED Activation record 'bbad3eabb3d64ab1ad3eabb3d61ab1a7' for entity 'logging-in-background'

您可以看到当我第一次调用该函数时,它只记录了“函数已启动”消息。 5 秒后它没有记录“延迟 5 秒后”消息。但随后,在第二次调用开始时,即第一次调用后 10 秒,它最终记录了与运行 9be7c 相关的“延迟 5 秒后”消息。 setTimeout 的回调似乎永远不会运行,直到最早在下次调用该操作时才会运行。

这是 Apache OpenWhisk 的设计方式,还是在操作完成后我没有正确地在后台运行代码?

【问题讨论】:

  • “Apache OpenWhisk 是这样设计的吗...” -- 您观察到并描述的是系统的预期工作方式。

标签: node.js ibm-cloud settimeout openwhisk ibm-cloud-functions


【解决方案1】:

您的函数在 setTimeout 完成之前返回。

在此处查看文档:

https://github.com/apache/openwhisk/blob/master/docs/actions-nodejs.md#creating-asynchronous-actions

正确的做法:

function main(args) {
    const runId = uuidv4().slice(31, 36);

    console.log(`function started (runId = ${runId})`);
    return new Promise(function(resolve, reject) {
       setTimeout(function() {
         console.log(`after 5s delay (runId = ${runId})`);
         resolve({ msg: `ok (runId = ${runId}) });
       }, 5000);
    })
}

如果使用 AWS lambda,当您将任务留在事件循环中时,该函数不会返回/终止请求,直到偶数循环为空。如果您使用特殊的上下文标志让函数急切返回,则不能保证后台处理会真正完成。

要初始化连接池并在调用之间重用它们,您可以将连接存储在全局变量中并确保它仅被初始化。

【讨论】:

  • 我实际上已经阅读了文档的那部分。但我并不是要创建一个在异步部分完成之前不会返回的操作。我正在尝试创建一个能够完成的操作,同时仍将事件循环上的工作留待以后完成。我的一个用例是在操作完成后累积数据以将大对象放入 COS 存储桶,这也将涉及去抖动。重点是完成一个动作,在内存中保留一些数据,在未来的动作调用中接受更多数据,然后最终将数据发送给 COS。OpenWhisk 可以做到这一点吗?
  • 还有一些常见的技术可以管理来自 FaaS 平台的持久数据库连接,因此“僵尸”容器不会保持连接打开,多个僵尸容器最终会达到数据库的连接限制。这是我在问题中提到的另一个用例。 OpenWhisk 也可以做到这一点对我来说很重要,否则我将无法在操作中安全地保留对连接的引用并将其重用于多次调用。我每次都必须关闭并重新打开连接。
  • 使用 OpenWhisk 执行此操作的方法是调用可以完成处理的异步操作。使用 npm openwhisk 包进行另一个调用,或者直接使用 REST API。对于更复杂的工作流程,您可能需要查看 OpenWhisk 编写器(如 AWS Step Functions)。使用 AWS lambda 当您将任务留在事件循环中时,该函数不会返回/终止请求,直到偶数循环为空。如果您使用特殊的上下文标志来允许函数急切返回,则不能保证后台处理会真正完成。
  • "关键是完成一个动作,在内存中保留一些数据,在以后的动作调用中接受更多数据,然后最终将数据发送给 COS。OpenWhisk 可以做到这一点吗?" --- 你可以离开函数内状态。例如,如果您声明一个全局变量,则它可用于重用该容器的函数的所有调用。您也可以使用这种方法(具有全局引用)来创建数据库连接或连接池。您不必在每次调用时都创建新连接。
  • 感谢您提出研究其他模式的建议,例如从我的操作中调用另一个操作,但我认为这也不适用于我的用例。我希望稍后运行的代码仍然可以访问第一个操作的状态。对于我的 COS 用例,我认为我唯一的选择是将该状态存储在外部数据库中,并进行第二次调度操作轮询该数据库,并在收集到足够多的对象后将对象放入存储桶。
【解决方案2】:

另一位用户通过解释 OpenWhisk 不会在操作完成后在事件循环中运行代码来帮助我理解发生了什么。因此,不可能做我想做的事情,我希望调用的每个操作都在操作完成后的某个时间检索存储在局部变量中的数据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    • 1970-01-01
    • 2017-08-07
    • 1970-01-01
    • 2018-01-01
    • 2017-08-09
    相关资源
    最近更新 更多