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:deleting
、aws:rep:updatetime
、aws: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 事件上触发了两次的主要内容,如果未能解决你的问题,请参考以下文章
在我的S3存储桶上执行getObject操作时,如何触发Lambda函数?