Docs 菜单
Docs 主页
/
MongoDB Atlas
/ / /

处理函数中的错误

在此页面上

  • 查看日志
  • 重试函数
  • 在错误处理块中递归调用函数
  • 使用数据库触发器重试

本页介绍了如何处理 Atlas Function 中的错误。

注意

使用 AWS EventBridge 进行数据库触发器的自定义错误处理

您可以使用AWS Eventbridge创建专门为数据库触发器创建的自定义错误处理程序。 有关详细信息,请参阅自定义错误处理。

您可以使用标准JavaScript错误处理技术(例如 try...catch 语句 )来处理函数错误。

function willThrowAndHandleError() {
try {
throw new Error("This will always happen");
} catch (err) {
console.error("An error occurred. Error message:" + err.message);
}
}
exports = willThrowAndHandleError;

您可以在 Logs 页面查看Atlas trigger调用的所有函数执行记录。 这包括由于错误而未能成功执行的函数。

有关更多信息,请参阅trigger日志。

Atlas Functions 没有内置的重试行为。

您可以添加自定义重试行为。例如,如果函数调用的第三方服务具有间歇性连接,并且您希望即使第三方服务器暂时关闭,函数也能重新执行,则您可能希望添加重试行为。

以下部分描述了向函数添加重试行为的可用策略:

您可以递归调用“函数”来处理可能失败的操作。

总体而言,此进程包括以下组成部分:

  • try 语句中执行要重试的操作,并在catch 语句中调用函数本身。

  • 要防止无限期执行,请设置最大重试次数。每次函数失败并进入 catch 语句时,增加当前重试次数的计数。在函数的当前重试次数达到最大重试次数时,停止递归执行。

  • 您可能还希望限制重试次数以减少某个时间范围内的执行总数。

下表描述了使用递归调用策略处理函数重试的一些优点和缺点。

优点
缺点
  • 所有重试逻辑都发生在一个函数内。

  • 函数可以在重试后返回一个值。

  • 最少的额外代码。

  • 所有重试必须在单个函数的最大执行时间内进行。

以下代码示例演示了通过在错误处理块中使用递归来重试函数的实施:

递归调用策略示例
// Utility function to suspend execution of current process
async function sleep(milliseconds) {
await new Promise((resolve) => setTimeout(resolve, milliseconds));
}
// Set variables to be used by all calls to `mightFail`
// Tip: You could also store `MAX_RETRIES` and `THROTTLE_TIME_MS`
// in App Services Values
const MAX_RETRIES = 5;
const THROTTLE_TIME_MS = 5000;
let currentRetries = 0;
let errorMessage = "";
async function mightFail(...inputVars) {
if (currentRetries === MAX_RETRIES) {
console.error(
`Reached maximum number of retries (${MAX_RETRIES}) without successful execution.`
);
console.error("Error Message:", errorMessage);
return;
}
let res;
try {
// operation that might fail
res = await callFlakyExternalService(...inputVars);
} catch (err) {
errorMessage = err.message;
// throttle retries
await sleep(THROTTLE_TIME_MS);
currentRetries++;
res = await mightFail(...inputVars);
}
return res;
}
exports = mightFail;

您还可以使用数据库触发器执行重试,并使用 MongoDB 集合跟踪以前失败的执行以重试函数。

总体而言,此进程包括以下组成部分:

  • 主函数,用于执行要重试的逻辑,包装在处理程序函数中。

  • 失败执行跟踪器 MongoDB 集合;用于跟踪主函数的失败执行。

  • 处理程序函数;用于调用主函数,并在函数失败时将其记录到失败执行跟踪器集合中。

  • 数据库触发器函数;每当处理程序函数将错误添加到失败执行跟踪器集合时,就会重新运行处理程序函数。

您可以使用一组处理程序函数、执行跟踪器集合和数据库触发器函数来支持多个主函数。

优点
缺点
  • 每次重试都是其自己的函数执行,具有自己的最大执行时间和资源。

  • 如果重试函数,则无法返回值。

  • 每个函数调用都需要调用函数两次,一次用于函数本身,一次用于重试处理程序。

  • 更复杂的逻辑,可能更难编写、调试和监控。

您可以在Atlas用户界面中或使用App Services CLI为函数创建重试机制。 以下过程将引导您创建处理程序、重试数据库trigger函数和主函数。

1

首先,创建调用主函数的处理程序函数 handleRetry

handleRetry 接受以下参数:

Parameter
类型
说明
functionToRetry
JavaScript 函数
要重试的函数。
functionName
字符串
您要重试的函数名称。
operationId
ObjectId
主函数执行的唯一标识符,包括重试。
previousRetries
数值
以前尝试执行主函数的次数。
...args
其余参数
传递给主函数的不确定数量的参数。

handleRetry 执行以下操作:

  1. 尝试在 try 语句中执行 functionToRetry。如果执行成功,则 handleRetry 将返回由以下内容返回的值 functionToRetry

  2. 如果在上一步中执行 functionToRetry 而引发错误,则catch 语句按如下方式处理该错误:

    1. 检查以前的重试次数是否等于允许的最大重试次数。如果两个数字相同,则函数会发生错误,因为已达到最大重试次数。该函数不再尝试重试。

    2. 创建一个函数执行日志条目对象以插入到数据库中。

    3. 获取对失败执行跟踪器集合的引用。

    4. 将函数日志执行日志条目插入失败执行跟踪器集合。 此插入操作会导致您将在下一步中创建的数据库trigger函数触发。

主函数是作为 functionToRetry 参数传递的。handleRetry 尝试执行主函数。如果执行失败,该函数尝试重试主函数。

  1. 导航至 Functions。单击按钮 Create New Function

  2. 在字段 Name 中,添加 handleRetry

  3. Function Editor 中添加以下代码,然后保存该函数:

    handleRetry.js
    // Tip: You could also put this in an App Services Value
    const MAX_FUNC_RETRIES = 5;
    async function handleRetry(
    functionToRetry,
    functionName,
    operationId,
    previousRetries,
    ...args
    ) {
    try {
    // Try to execute the main function
    const response = await functionToRetry(...args);
    return response;
    } catch (err) {
    // Evaluates if should retry function again.
    // If no retry, throws error and stops retrying.
    if (previousRetries === MAX_FUNC_RETRIES) {
    throw new Error(
    `Maximum number of attempts reached (${MAX_FUNC_RETRIES}) for function '${functionName}': ${err.message}`
    );
    }
    // Build function execution log entry for insertion into database.
    const logEntry = {
    operationId,
    errorMessage: err.message,
    timestamp: new Date(),
    retries: previousRetries + 1,
    args,
    functionName,
    };
    // Get reference to database collection
    const executionLog = context.services
    .get("mongodb-atlas")
    .db("logs")
    .collection("failed_execution_logs");
    // Add execution log entry to database
    await executionLog.insertOne(logEntry);
    return;
    }
    }
    exports = handleRetry;
  1. 将以下内容添加到functions/config.json中:

    functions/config.json
    [
    {
    "name": "handleRetry",
    "private": true,
    "run_as_system": true
    }
    // ...other configuration
    ]
  2. 为函数functions/handleRetry.js创建文件:

    functions/handleRetry.js
    // Tip: You could also put this in an App Services Value
    const MAX_FUNC_RETRIES = 5;
    async function handleRetry(
    functionToRetry,
    functionName,
    operationId,
    previousRetries,
    ...args
    ) {
    try {
    // Try to execute the main function
    const response = await functionToRetry(...args);
    return response;
    } catch (err) {
    // Evaluates if should retry function again.
    // If no retry, throws error and stops retrying.
    if (previousRetries === MAX_FUNC_RETRIES) {
    throw new Error(
    `Maximum number of attempts reached (${MAX_FUNC_RETRIES}) for function '${functionName}': ${err.message}`
    );
    }
    // Build function execution log entry for insertion into database.
    const logEntry = {
    operationId,
    errorMessage: err.message,
    timestamp: new Date(),
    retries: previousRetries + 1,
    args,
    functionName,
    };
    // Get reference to database collection
    const executionLog = context.services
    .get("mongodb-atlas")
    .db("logs")
    .collection("failed_execution_logs");
    // Add execution log entry to database
    await executionLog.insertOne(logEntry);
    return;
    }
    }
    exports = handleRetry;
  3. 部署更改:

    运行以下命令以部署更改:

    appservices push
2
  1. 导航至Triggers页面:

    1. 如果尚未显示,请从导航栏上的 Organizations 菜单中选择包含项目的组织。

    2. 如果尚未显示,请从导航栏的 Projects 菜单中选择您的项目。

    3. 在侧边栏中,单击 Services 标题下的 Triggers

      会显示触发器页面。

  2. 单击 Add a Trigger(连接)。

  3. 使用以下配置创建触发器:

    字段
    名称
    您选择的名称(例如:retryOperation
    已启用
    Skip Events on Re-Enable
    事件排序
    集群名称
    您选择的名称(例如:mongodb-atlas
    Database Name
    您选择的名称(例如:logs
    集合名称
    您选择的名称(例如:failed_execution_logs
    操作类型
    Insert
    完整文档
    文档原像
    No
    Select an Event Type(选择事件类型)
    function
    function
    单击 + New Function。有关函数内容,请参阅以下信息。
    高级配置
    不适用 — 无需高级配置。
  4. 现在将代码添加到trigger调用的函数中。

    函数retryOperation将重试处理程序发布到失败执行跟踪器集合的文档作为参数logEntry 。 然后, retryOperation使用context.functions.execute()使用来自logEntry的信息调用主函数。

    1. 在字段 Function Name 中,添加 retryOperationDbTrigger

    2. 对于字段Function,添加以下代码,然后保存trigger :

      functions/retryOperationDbTrigger.js
      async function retryOperation({ fullDocument: logEntry }) {
      // parse values from log entry posted to database
      const { args, retries, functionName, operationId } = logEntry;
      // Re-execute the main function
      await context.functions.execute(functionName, ...args, operationId, retries);
      }
      exports = retryOperation;
  1. 对MongoDB Atlas用户进行身份验证:

    使用MongoDB Atlas Administration API密钥登录App Services CLI:

    appservices login --api-key="<API KEY>" --private-api-key="<PRIVATE KEY>"
  2. 拉取应用的最新配置文件:

    运行以下命令以获取配置文件的本地副本:

    appservices pull --remote=<App ID>

    默认,该命令会将文件提取到当前工作目录中。 您可以使用可选的--local标志指定目录路径。

  3. 为数据库trigger添加配置。 有关详细信息,请参阅trigger配置参考。

    triggers/retryOperation.json
    {
    "name": "retry",
    "type": "DATABASE",
    "config": {
    "operation_types": ["INSERT"],
    "database": "logs",
    "collection": "failed_execution_logs",
    "service_name": "mongodb-atlas",
    "project": {},
    "full_document": true,
    "full_document_before_change": false,
    "unordered": false,
    "skip_catchup_events": false
    },
    "disabled": false,
    "event_processors": {
    "FUNCTION": {
    "config": {
    "function_name": "retryOperationDbTrigger"
    }
    }
    }
    }
  4. 现在将代码添加到trigger调用的 functions/config.json 函数中。

    函数retryOperation将重试处理程序发布到失败执行跟踪器集合的文档作为参数logEntry 。 然后, retryOperation使用context.functions.execute()使用来自logEntry的信息调用主函数。

    functions/config.json
    [
    // ...other configuration
    {
    "name": "retryOperationDbTrigger",
    "private": true
    }
    ]
  5. 将以下代码添加到文件functions/retryOperationDbTrigger.js中:

    retryOperationDbTrigger.js
    async function retryOperation({ fullDocument: logEntry }) {
    // parse values from log entry posted to database
    const { args, retries, functionName, operationId } = logEntry;
    // Re-execute the main function
    await context.functions.execute(functionName, ...args, operationId, retries);
    }
    exports = retryOperation;
  6. 部署更改:

    运行以下命令以部署更改:

    appservices push
3

现在您有了函数处理程序和重试数据库触发器函数,您可以编写主函数。

在以下示例中,函数在执行加法时随机出现错误。执行此逻辑的 JavaScript 函数如下:

  • getRandomOneTwoThree():用于为示例生成错误的辅助函数。

  • additionOrFailure():使用主逻辑的函数。

调用重试处理程序包装的 additionOrFailure() 是在导出的函数 additionWithRetryHandler() 中进行的。所有使用重试处理函数的函数应类似于该函数。

必须包含正确的参数,才能使此函数与其余重试逻辑一起工作。这些参数包括:

Parameter
类型
说明
...args
其余参数
使用主逻辑传递给函数的零个或多个参数。在本例中,additionOrFailure()num1num2 这两个数字进行相加。
operationId
函数调用和重试的唯一标识符。将默认值设置为 new BSON.ObjectId()
retries
数值
将默认值设置为 0。

additionWithRetryHandler的主体是context.functions.execute() handleRetry ,而该重试处理程序又会调用additionOrFailure 。 您传递给context.functions.execute()的参数如下:

Argument
类型
说明
"handleRetry"
字符串
您定义的函数名称,用于调用主函数,并在主函数未正确执行时发布到重试日志。
additionOrFailure
JavaScript 函数
handleRetry() 调用的主函数。
operationId
BSON.ObjectId
作为参数从 additionWithRetryHandler()operationId 参数中传入。
retries
数值
作为参数从 additionWithRetryHandler()retries 参数中传入。
...args
展开参数
使用主逻辑传递给函数的零个或更多参数。作为参数从 additionWithRetryHandler()...args 参数中传入
  1. 在字段 Function Name 中,添加 additionWithRetryHandler

  2. 对于字段Function ,添加以下代码:

    additionWithRetryHandler.js
    // randomly generates 1, 2, or 3
    function getRandomOneTwoThree() {
    return Math.floor(Math.random() * 3) + 1;
    }
    function additionOrFailure(num1, num2) {
    // Throw error if getRandomOneTwoThree returns 1
    const rand = getRandomOneTwoThree();
    if (rand === 1) throw new Error("Uh oh!!");
    const sum = num1 + num2;
    console.log(`Successful addition of ${num1} + ${num2}. Result: ${sum}`);
    // Otherwise return the sum
    return sum;
    }
    async function additionWithRetryHandler(
    inputVar1,
    inputVar2,
    // create a new `operation_id` if one not provided
    operationId = new BSON.ObjectId(),
    // count number of attempts
    retries = 0
    ) {
    const res = await context.functions.execute(
    "handleRetry",
    additionOrFailure,
    "additionWithRetryHandler", // MUST BE NAME OF FUNCTION
    operationId,
    retries,
    inputVar1,
    inputVar2
    );
    return res;
    }
    exports = additionWithRetryHandler;
  3. 单击 Save(连接)。

  1. 将函数元数据添加到functions/config.json

    functions/config.json
    [
    // ...other configuration
    {
    "name": "additionWithRetryHandler",
    "private": false
    }
    ]
  2. 将以下代码添加到文件functions/additionWithRetryHandler.js中:

    functions/additionWithRetryHandler.js
    // randomly generates 1, 2, or 3
    function getRandomOneTwoThree() {
    return Math.floor(Math.random() * 3) + 1;
    }
    function additionOrFailure(num1, num2) {
    // Throw error if getRandomOneTwoThree returns 1
    const rand = getRandomOneTwoThree();
    if (rand === 1) throw new Error("Uh oh!!");
    const sum = num1 + num2;
    console.log(`Successful addition of ${num1} + ${num2}. Result: ${sum}`);
    // Otherwise return the sum
    return sum;
    }
    async function additionWithRetryHandler(
    inputVar1,
    inputVar2,
    // create a new `operation_id` if one not provided
    operationId = new BSON.ObjectId(),
    // count number of attempts
    retries = 0
    ) {
    const res = await context.functions.execute(
    "handleRetry",
    additionOrFailure,
    "additionWithRetryHandler", // MUST BE NAME OF FUNCTION
    operationId,
    retries,
    inputVar1,
    inputVar2
    );
    return res;
    }
    exports = additionWithRetryHandler;
  3. 部署更改:

    运行以下命令以部署更改:

    appservices push

现在,在您调用 additionWithRetryHandler 时,如果失败,函数将重试。

后退

外部依赖项