本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
中的冲突检测和解决 AWS AppSync
当发生并发写入时 AWS AppSync,您可以配置冲突检测和冲突解决策略以适当地处理更新。冲突检测确定变更是否与数据来源中的实际写入项目发生冲突。通过将conflictDetection
字段中的值设置为, SyncConfig 即可启用冲突检测VERSION
。
冲突解决是在检测到冲突时执行的操作。这是通过在中设置 “冲突处理程序” 字段来确定的 SyncConfig。有三种冲突解决策略:
-
OPTIMISTIC_CONCURRENCY
-
AUTOMERGE
-
LAMBDA
AWS AppSync 在写入操作期间,版本会自动递增,不应由客户端或配置了支持版本的数据源的解析器外部进行修改。否则会改变系统的一致性行为,并可能导致数据丢失。
乐观的并发性
乐观并发是一种冲突解决策略, AWS AppSync 它提供了版本化数据源。当冲突解决程序设置为乐观并发时,如果检测到传入的变更具有与对象的实际版本不同的版本,则冲突处理程序将简单地拒绝传入请求。在 GraphQL 响应中,将提供服务器上具有最新版本的现有项目。然后,客户端需要在本地处理此冲突,并使用项目的更新版本重试变更。
Automerges
Automerge 为开发人员提供了配置冲突解决策略的简单方法,而无需编写客户端逻辑来手动合并其他策略无法处理的冲突。Automerge 在合并数据以解决冲突时遵守严格的规则集。Automerge 的原则围绕 GraphQL 字段的底层数据类型。这些特性如下所示:
-
标量字段上的冲突:GraphQL 标量或不是集合(即 List、Set、Map)的任何字段。拒绝标量字段的传入值并选择服务器中现有的值。
-
列表上的冲突:GraphQL 类型和数据库类型是列表。将传入列表与服务器中的现有列表连接起来。传入变更中的列表值将附加到服务器中列表的末尾。将保留重复的值。
-
集合上的冲突:GraphQL 类型是一个列表,数据库类型是一个集合。使用传入集合和服务器中的现有集合应用集合合并。这符合集合的属性,意味着没有重复的条目。
-
当传入变更在项目中添加新字段或针对值为
null
的字段进行变更时,请将其合并到现有项目中。 -
映射上的冲突:当数据库中的底层数据类型是映射(即键值文档)时,在解析和处理映射的每个属性时应用上述规则。
Automerge 旨在使用更新版本自动检测、合并和重试请求,从而使客户端无需手动合并任何冲突的数据。
为了显示 Automerge 如何处理标量类型上的冲突的示例,我们将使用以下记录作为起点。
{ "id" : 1, "name" : "Nadia", "jersey" : 5, "_version" : 4 }
现在,传入的变更可能正在尝试更新该项目,但使用的是较旧版本,因为客户端尚未与服务器同步。它类似于以下内容:
{ "id" : 1, "name" : "Nadia", "jersey" : 55, "_version" : 2 }
请注意传入请求中的过时版本 2。在此流程中,Automerge 将通过拒绝“球衣”字段更新为“55”来合并数据,并将其值保持在“5”,从而导致下面的项目图像保存在服务器中。
{ "id" : 1, "name" : "Nadia", "jersey" : 5, "_version" : 5 # version is incremented every time automerge performs a merge that is stored on the server. }
鉴于上面显示的具有版本 5 的项目状态,现在假设一个传入的变更尝试使用以下图像改变项目:
{ "id" : 1, "name" : "Shaggy", "jersey" : 5, "interests" : ["breakfast", "lunch", "dinner"] # underlying data type is a Set "points": [24, 30, 27] # underlying data type is a List "_version" : 3 }
传入的变更中有三个兴趣点。名称(一个标量)已被更改,但添加了两个新字段“兴趣”(集合)和“分数”(列表)。在这种情况下,将检测到由于版本不匹配而导致的冲突。Automerge 将遵循其属性并拒绝名称更改,因为它是一个标量并添加到非冲突字段。这将导致保存在服务器中的项目显示如下。
{ "id" : 1, "name" : "Nadia", "jersey" : 5, "interests" : ["breakfast", "lunch", "dinner"] # underlying data type is a Set "points": [24, 30, 27] # underlying data type is a List "_version" : 6 }
使用具有版本 6 的项目的更新图像,现在假设传入的变更(版本不匹配)尝试将项目转换为以下内容:
{ "id" : 1, "name" : "Nadia", "jersey" : 5, "interests" : ["breakfast", "lunch", "brunch"] # underlying data type is a Set "points": [30, 35] # underlying data type is a List "_version" : 5 }
在这里,我们观察到“兴趣”的传入字段有一个存在于服务器中的重复值和两个新值。在这种情况下,由于底层数据类型是集合,Automerge 会将服务器中现有的值与传入请求中的值合并,并剔除任何重复项。同样,“分数”字段上也存在冲突,其中有一个重复值和一个新值。但是,由于这里的底层数据类型是列表,Automerge 会简单地将传入请求中的所有值附加到服务器中已存在的值的末尾。存储在服务器上的结果合并图像如下所示:
{ "id" : 1, "name" : "Nadia", "jersey" : 5, "interests" : ["breakfast", "lunch", "dinner", "brunch"] # underlying data type is a Set "points": [24, 30, 27, 30, 35] # underlying data type is a List "_version" : 7 }
现在,让我们假设存储在服务器中的项目(版本 8)显示如下。
{ "id" : 1, "name" : "Nadia", "jersey" : 5, "interests" : ["breakfast", "lunch", "dinner", "brunch"] # underlying data type is a Set "points": [24, 30, 27, 30, 35] # underlying data type is a List "stats": { "ppg": "35.4", "apg": "6.3" } "_version" : 8 }
但是传入的请求尝试使用以下图像更新该项目,从而再次出现版本不匹配的情况:
{ "id" : 1, "name" : "Nadia", "stats": { "ppg": "25.7", "rpg": "6.9" } "_version" : 3 }
现在,在这种情况下,我们可以看到服务器中已经存在的字段丢失(兴趣、分数、球衣)。此外,正在编辑映射“统计”中的“ppg”值,添加了一个新值“rpg”,并省略了“apg”。Automerge 保留已省略的字段(注意:如果打算删除字段,则必须使用匹配版本再次尝试请求),以便它们不会丢失。它也会将相同的规则应用于映射中的字段,因此对“ppg”的更改将被拒绝,而“apg”被保留,并添加一个新字段“rpg”。存储在服务器中的结果项目现在将显示为:
{ "id" : 1, "name" : "Nadia", "jersey" : 5, "interests" : ["breakfast", "lunch", "dinner", "brunch"] # underlying data type is a Set "points": [24, 30, 27, 30, 35] # underlying data type is a List "stats": { "ppg": "35.4", "apg": "6.3", "rpg": "6.9" } "_version" : 9 }
Lambda
有几种 Lambda 解析策略可供选择:
-
RESOLVE
: 用响应有效载荷中提供的新物品替换现有物品。您一次只能对单个项目重试同一操作。当前受 DynamoDBPutItem
和UpdateItem
支持。 -
REJECT
: 拒绝该突变并返回 GraphQL 响应中现有项目的错误。当前受 DynamoDBPutItem
、UpdateItem
和DeleteItem
支持。 -
REMOVE
:移除现有项目。当前受 DynamoDBDeleteItem
支持。
Lambda 调用请求
AWS AppSync DynamoDB 解析器调用中指定的 Lambda 函数。LambdaConflictHandlerArn
它将使用在数据来源上配置的相同 service-role-arn
。调用的负载具有以下结构:
{ "newItem": { ... }, "existingItem": {... }, "arguments": { ... }, "resolver": { ... }, "identity": { ... } }
字段定义如下:
-
newItem
-
预览项目(如果变更成功)。
-
existingItem
-
该项目当前位于 DynamoDB 表中。
-
arguments
-
来自 GraphQL 变更的参数。
-
resolver
-
有关 AWS AppSync 解析器的信息。
-
identity
-
有关调用方的信息。如果使用API密钥访问,则此字段设置为空。
有效负载示例:
{ "newItem": { "id": "1", "author": "Jeff", "title": "Foo Bar", "rating": 5, "comments": ["hello world"], }, "existingItem": { "id": "1", "author": "Foo", "rating": 5, "comments": ["old comment"] }, "arguments": { "id": "1", "author": "Jeff", "title": "Foo Bar", "comments": ["hello world"] }, "resolver": { "tableName": "post-table", "awsRegion": "us-west-2", "parentType": "Mutation", "field": "updatePost" }, "identity": { "accountId": "123456789012", "sourceIp": "x.x.x.x", "username": "AIDAAAAAAAAAAAAAAAAAA", "userArn": "arn:aws:iam::123456789012:user/appsync" } }
Lambda 调用响应
对于 PutItem
和 UpdateItem
解决冲突
RESOLVE
变更。响应必须采用以下格式。
{ "action": "RESOLVE", "item": { ... } }
item
字段表示将用于替换底层数据来源中的现有项目的对象。如果在 item
中包含主键和同步元数据,则将被忽略。
REJECT
变更。响应必须采用以下格式。
{ "action": "REJECT" }
对于 DeleteItem
冲突解决
REMOVE
项目。响应必须采用以下格式。
{ "action": "REMOVE" }
REJECT
变更。响应必须采用以下格式。
{ "action": "REJECT" }
下面的示例 Lambda 函数检查谁进行调用以及解析器名称。如果是由 resolver 创建的jeffTheAdmin
,REMOVE
则是 DeletePost解析器的对象,或者RESOLVE
是与 Update/Put 解析器的新项目冲突。如果不是,则变更是 REJECT
。
exports.handler = async (event, context, callback) => { console.log("Event: "+ JSON.stringify(event)); // Business logic goes here. var response; if ( event.identity.user == "jeffTheAdmin" ) { let resolver = event.resolver.field; switch(resolver) { case "deletePost": response = { "action" : "REMOVE" } break; case "updatePost": case "createPost": response = { "action" : "RESOLVE", "item": event.newItem } break; default: response = { "action" : "REJECT" }; } } else { response = { "action" : "REJECT" }; } console.log("Response: "+ JSON.stringify(response)); return response; }
错误
以下是在冲突解决过程中可能出现的错误列表:
-
ConflictUnhandled
-
冲突检测发现版本不匹配情况,并且冲突处理程序拒绝变更。
示例:使用乐观并发冲突处理程序解决冲突。或者,Lambda 冲突处理程序返回
REJECT
。 -
ConflictError
-
尝试解决冲突时发生内部错误。
示例:Lambda 冲突处理程序返回格式错误的响应。或者,无法调用 Lambda 冲突处理程序,因为找不到提供的资源
LambdaConflictHandlerArn
。 -
MaxConflicts
-
已达到解决冲突的最大重试次数。
示例:同一对象上的并发请求太多。在解决冲突之前,另一个客户端将对象更新为新版本。
-
BadRequest
-
客户端尝试更新元数据字段(
_version
、_ttl
、_lastChangedAt
、_deleted
)。示例:客户端尝试使用更新
_version
突变来更新对象。 -
DeltaSyncWriteError
-
写入增量同步记录失败。
示例:变更成功,但尝试写入增量同步表时出现内部错误。
-
InternalFailure
-
出现内部错误。
UnsupportedOperation
-
不支持的操作 '
X
'。 数据源版本控制仅支持以下操作(TransactGetItems、、、扫描PutItem、查询BatchGetItem、、、、GetItemDeleteItemUpdateItem、同步)。示例:在启用冲突检测/解决功能的情况下使用某些事务和批量操作。目前不支持这些操作。
CloudWatch 日志
如果启用 AWS AppSync API了 CloudWatch 日志,且日志设置为字段级日志enabled
,字段级日志的日志级别设置为ALL
,则 AWS AppSync 会向日志组发送冲突检测和解决方案信息。有关日志消息格式的信息,请参阅冲突检测和同步日志记录的文档。