Lambda 函数在 DynamoDB 事件上触发了两次

Posted

技术标签:

【中文标题】Lambda 函数在 DynamoDB 事件上触发了两次【英文标题】:Lambda function is triggering twice on DynamoDB event 【发布时间】:2019-08-02 15:33:26 【问题描述】:

我有附加到 DynamoDB 更改事件的 Lambda 函数。 当我更改/修改 DynamoDB 的 Test-machines 表中的项目时,Lambda 会触发两次。

我正在将IsMachineOn 的值从True 修改为False,这是两次触发Test-Machine-On-alert-status Lambda 函数。

我不明白为什么两次 lambda 是触发器。

我观察到 Lambda 的 event 参数中的 records 有一个小的变化。

对于第一次触发

NewImage["IsMachineOn"]["BOOL"] 的值为 False

OldImage["IsMachineOn"]["BOOL"] 的值为 True

对于第二次触发

NewImage["IsMachineOn"]["BOOL"] 的值为 False

OldImage["IsMachineOn"]["BOOL"] 的值为 False

我在NewImage["IsMachineOn"]["BOOL"]==False 上有业务逻辑,因此我的业务逻辑运行了两次。

有两件事:

    为什么 Lambda 运行两次? 有什么办法可以解决此问题?

【问题讨论】:

为了保证至少交付一次,这个多次调用的东西将probably happen。关键是,我们是否应该认为您的 Lambda 函数是非幂等的?如果是这样,将其设为一个将是一种解决方法。 @vahdet:我的 Lambda 函数不是幂等的。每次请求 id 都不一样。 这种行为并不一定会让你的代码具有幂等性,但无论如何;如果你严格要求只发射一次,我现在想不出解决办法。 您的逻辑当然应该测试NewImage["IsMachineOn"]["BOOL"] == False && NewImage["IsMachineOn"]["BOOL"] != OldImage["IsMachineOn"]["BOOL"](现在关闭,这也是一个状态更改事件)......但听起来好像第二个不同的更新正在触发第二个事件,因此,您可能应该查看其他属性,以确定第二个事件触发器的性质。这不能 - 根据定义 - 是同一事件上的第二个 Lambda 触发器,如果​​新旧的一个不同而另一个相同。 关于这个主题有一篇很好的博文:cloudonaut.io/… 从中学到的主要内容:确保你的 Lambda 函数是幂等的,并且可以处理潜在的多次执行。 【参考方案1】:

我们在使用全局表在多个区域的 dynamodb 表之间同步数据时发现了这个问题。我们的假设是,在区域之间同步数据后,第二次推送是由全局表进行的。我写了一个简单的代码来检查新旧图像是否真的不同,只有当它们不同时才处理事件

def check_if_dynamo_entities_are_same(dyanmoStreamEvent):
    '''copying so that we dont change the incoming event'''
    dyanmoStreamEventCopy = copy.deepcopy(dyanmoStreamEvent)
    if( not 'NewImage' in dyanmoStreamEventCopy['dynamodb'] or not 'OldImage' in dyanmoStreamEventCopy['dynamodb']):
        logger.info("one of newimage or oldimage is not present returning true")
        return False
    remove_aws_keys(dyanmoStreamEventCopy['dynamodb']['NewImage'])
    remove_aws_keys(dyanmoStreamEventCopy['dynamodb']['OldImage'])
    return compare_two_json(dyanmoStreamEventCopy['dynamodb']['NewImage'], dyanmoStreamEventCopy['dynamodb']['OldImage'])

def remove_aws_keys(dic):
    for k in dic.copy():
        if k.startswith('aws:'):
            logger.info("poping key=%s", k)
            dic.pop(k)

def ordered(obj):
    if isinstance(obj, dict):
        return sorted((k, ordered(v)) for k, v in obj.items())
    if isinstance(obj, list):
        return sorted(ordered(x) for x in obj)
    else:
        return obj


def compare_two_json(json1, json2):
    """This method return true or false if the given jsons are equal or not.
    This has been taken from https://***.com/a/25851972/3892213"""
    return ordered(json1) == ordered(json2)

【讨论】:

【参考方案2】:

我也会检查记录是否已更改! 因此我在 Python 3.6 中编写了以下代码

old_sites = set()
    new_sites = set()

    # Calculate the disjoint quantity of NEW & OLD mappings
    for image_name in ['OldImage', 'NewImage']:
        if record['dynamodb'] is not None and image_name in record['dynamodb']:
            ddb_entry = record['dynamodb'][image_name]
            mappings = ddb_entry[value_key]['L']
            print(f"mappings: mappings")

            for mapping in mappings:
                old_sites.add(mapping['S']) if image_name == 'OldImage' else new_sites.add(mapping['S'])

    changed_mappings = old_sites.symmetric_difference(new_sites)

【讨论】:

【参考方案3】:

我们的 DynamoDB 全局表遇到了同样的问题。我们观察到双重事件仅发生在您创建update 的区域,其他区域仍然收到 1 个事件,这很棒。

您得到 2 个事件的原因是 DynamoDB 需要维护一些内置字段以防止区域无限地相互更新的循环。

    第一个事件是更新存储的 real 对象/字段的属性,在您的情况下是 IsMachineOn,它从 true 更改为 false

    第二个事件正在更新特殊属性,例如aws:rep:deletingaws:rep:updatetimeaws:rep:updateregion。这就是为什么您会看到旧/新图像都有 IsMachineOn 作为 false 这是 new 值。

希望这有助于澄清一些事情。这让我困惑了好几个小时。

TL;DR...

通常您可以只比较旧/新图像的aws:rep:updatetime,如果它们相同,那么就是更新 aws internal 字段的事件,因此您可以忽略。

在我们的用例中,我们依靠aws:rep:updateregion 来确保某些逻辑只运行一次(而不是在多个区域中),因此我们必须比较旧/新的aws:rep:updatetime 以忽略第一个具有以前的地区信息。好消息是两个事件的 图像都具有我们存储的对象的正确值。

2020 年 7 月 23 日更新

我们注意到的另一个重要点是,如果您使用put() 更新/创建记录,则第一个事件的新图像以及第二个事件的旧图像中将缺少 aws 内置字段.如果您依赖这些字段,最好将update() 用于现有记录,以确保它们始终存在。

【讨论】:

以上是关于Lambda 函数在 DynamoDB 事件上触发了两次的主要内容,如果未能解决你的问题,请参考以下文章

DynamoDB 流 和 AWS Lambda 触发器

从 DynamoDB 触发 lambda 函数

在python中自动测试aws lambda函数

在我的S3存储桶上执行getObject操作时,如何触发Lambda函数?

如何使用放大框架/cli 配置 dynamodb-to-lambda 触发器

从 lambda 函数触发 Appsync 突变