Amazon DynamoDB
开发人员指南 (API Version 2012-08-10)
AWS 服务或AWS文档中描述的功能,可能因地区/位置而异。点 击 Getting Started with Amazon AWS to see specific differences applicable to the China (Beijing) Region.

DAX 和 DynamoDB 一致性模型

DAX 是一项直写缓存服务,旨在简化将缓存添加到 Amazon DynamoDB 表的过程。由于 DAX 是独立于 DynamoDB 运行的,因此您务必同时了解 DAX 和 DynamoDB 的一致性模型,以确保您的应用程序的行为方式符合预期。

在很多使用案例中,您的应用程序使用 DAX 的方式将影响 DAX 集群内的数据的一致性以及 DAX 和 DynamoDB 之间的数据的一致性。

DAX 集群节点之间的一致性

要为您的应用程序实现高可用性,建议您为 DAX 集群预置至少两个节点 (理想情况下为三个或更多),然后将这些节点置于某个区域内的多个可用区中。

当您的 DAX 集群正在运行时,它将复制该集群中所有节点之间的数据 (假定您已配置多个节点)。考虑一个使用 DAX 成功执行 UpdateItem 的应用程序。这会导致使用新值修改主节点中的项目缓存;该值随后将复制到该集群中的所有其他节点。此复制具有最终一致性,并且通常只需不到一秒即可完成。

在这种情况下,两个客户端可从同一 DAX 集群读取同一键但接收不同的值,具体取决于每个客户端访问的节点。当更新已在集群中的所有节点中完全复制后,这些节点将全部具有一致性。(请注意,此行为类似于 DynamoDB 的最终一致性。)

如果您要构建一个使用 DAX 的应用程序,则应采用可容忍最终一致的数据的方式设计该应用程序。

DAX 项目缓存行为

每个 DAX 集群具有两个不同的缓存 - 一个项目缓存和一个查询缓存。 (有关更多信息,请参阅 概念。)本节将介绍在 DAX 项目缓存中进行读取和写入的一致性影响。

读取的一致性

利用 Amazon DynamoDB,GetItem 操作将默认执行最终一致性读取。如果您将 GetItem 与 DynamoDB 客户端结合使用,然后立即尝试读取同一项目,则可能会看到数据在更新前的样子。这是由于所有 DynamoDB 存储位置中都发生了传播延迟。一致性通常在几秒内即可实现,因此如果您重试读取操作,您就可能看到更新后的项目。

当您将 GetItem 与 DAX 客户端结合使用时,操作过程将如下所示:

  1. DAX 客户端发出 GetItem 请求。DAX 尝试从项目缓存读取请求的项目。如果该项目在缓存中 (缓存命中),则 DAX 会将其返回到应用程序。

  2. 如果该项目不可用 (缓存未命中),则 DAX 会针对 DynamoDB 执行最终一致性 GetItem 操作。

  3. DynamoDB 返回请求的项目,DAX 将该项目存储在项目缓存中。

  4. DAX 将该项目返回到应用程序。

  5. (未显示) 如果 DAX 集群包含多个节点,则该项目将复制到集群中的所有其他节点。

该项目将保留在 DAX 项目缓存中,具体取决于缓存的 TTL 设置和 LRU 算法 (请参阅概念)。但是,在此期间,DAX 将不会从 DynamoDB 再次读取该项目。如果其他人使用 DynamoDB 客户端更新了该项目,从而完全绕过 DAX,则使用 DAX 客户端的 GetItem 请求将生成与使用 DynamoDB 客户端的同一 GetItem 请求不同的结果。在这种情况下,DAX 和 DynamoDB 将对同一键保留不一致的值,直到 DAX 项目的 TTL 过期。

如果应用程序修改了基础 DynamoDB 表中的数据,从而绕过 DAX,则该应用程序需要预测并容忍可能出现的数据不一致。

注意

GetItem 之外,DAX 客户端还支持 BatchGetItem 请求。BatchGetItem 实际上是围绕一个或多个 GetItem 请求的包装器,因此,DAX 将这些请求均视为单独的 GetItem 操作。

写入的一致性

DAX 是一种直写缓存,可简化使 DAX 项目缓存与基础 DynamoDB 表保持一致的过程。

DAX 客户端支持与 DynamoDB 相同的写入 API 操作 (PutItemUpdateItemDeleteItemBatchWriteItem)。当您将这些操作与 DAX 客户端结合使用时,将在 DAX 和 DynamoDB 中修改这些项目。DAX 将在项目缓存中更新这些项目,无论这些项目的 TTL 值是什么。

例如,假设您从 DAX 客户端发出了一个从 ProductCatalog 表读取某个项目的 GetItem 请求。(分区键为 Id;没有排序键。)您检索到了 Id101 的项目;该项目的 QuantityOnHand 值为 42。DAX 使用特定 TTL 将该项目存储在项目缓存中;在本示例中,让我们假定 TTL 为 10 分钟。3 分钟后,另一个应用程序使用 DAX 客户端更新了同一项目,因此该项目的 QuantityOnHand 值现为 41。假设该项目没有再次更新,则在下一个 10 分钟期间内,同一项目的任何后续读取将返回 QuantityOnHand 的缓存值 (41)。

DAX 如何处理写入

DAX 适用于需要高性能读取的应用程序。作为直写缓存,DAX 允许您直接发起写入操作,以便让您的写入立即反映在项目缓存中,从而让写入操作直接反映在项目缓存中。您无需管理缓存失效逻辑,因为 DAX 会为您处理它。

DAX 支持以下写入操作:PutItemUpdateItemDeleteItemBatchWriteItem。当您向 DAX 发送其中一项请求时,它会执行以下操作:

  • DAX 将请求发送到 DynamoDB。

  • DynamoDB 回复 DAX,确认写入成功。

  • DAX 将项目写入到项目缓存。

  • DAX 将成功信息返回到请求者。

如果对 DynamoDB 的写入出于任何原因 (包括限制) 失败,则项目将不会缓存在 DAX 中,并且有关失败的异常信息将返回到请求者。这确保了数据在首次成功写入到 DynamoDB 之前不会写入到 DAX 缓存。

注意

每对 DAX 写入一次都会修改项目缓存的状态;但是,对项目缓存的写入不会影响查询缓存。(DAX 项目缓存和查询缓存用于不同的目的,并且相互独立运行。)

DAX 查询缓存行为

DAX 将来自 QueryScan 请求的结果缓存在 DAX 的查询缓存中;但是,这些结果完全不会影响项目缓存。当您的应用程序使用 DAX 发起 QueryScan 请求时,结果集将保存在查询缓存中而不是项目缓存中。您无法通过执行 Scan 操作来“预热”项目缓存,因为项目缓存和查询缓存是独立的实体。

查询-更新-查询的一致性

对项目缓存或基础 DynamoDB 表进行更新不会使存储在查询缓存中的结果失效或修改这些结果。

为了说明这种情况,请考虑以下情形:应用程序使用名为 DocumentRevisions 的表,该表将 DocId 作为分区键,并将 RevisionNumber 作为排序键。

  1. 某个客户端针对 RevisionNumber 大于或等于 5 的所有项目发出了一个对 DocId 101Query。DAX 将结果集存储在查询缓存中,并将结果集返回到用户。

  2. 该客户端针对 RevisionNumber 值为 20 的 DocId 101 发出了一个 PutItem 请求。

  3. 该客户端发出了步骤 1 中所述的同一 Query (DocId 101RevisionNumber >= 5)。

在这种情况下,步骤 3 中发出的 Query 的缓存结果集将与步骤 1 中缓存的结果集相同。原因是 DAX 不会基于对各个项目的更新使 QueryScan 结果集失效。当 Query 的 TTL 过期时,步骤 2 中的 PutItem 操作将仅反映在 DAX 查询缓存中。

您的应用程序应考虑查询缓存的 TTL 值,以及您的应用程序能够容忍查询缓存与项目缓存之间的不一致结果的时长。

强一致性读取

要执行强一致性读取请求,请将 ConsistentRead 参数设置为 true。DAX 会将强一致性读取请求传递到 DynamoDB。当收到来自 DynamoDB 的响应时,DAX 会将结果返回到客户端,但不会缓存结果。DAX 无法自行处理强一致性读取,因为它未紧密耦合到 DynamoDB。因此,来自 DAX 的任何后续读取必须是最终一致性读取,并且任何后续的强一致性读取必须传递到 DynamoDB。

逆向缓存

DAX 在项目缓存和查询缓存中都支持逆向缓存输入。逆向缓存输入 在 DAX 无法在基础 DynamoDB 表中找到请求的项目时发生。DAX 不会生成错误,而会缓存空结果并将该结果返回到用户。

例如,假设某个应用程序向某个 DAX 集群发送了一个 GetItem 请求,并且 DAX 项目缓存中没有匹配的项目。这将导致 DAX 从基础 DynamoDB 表读取对应的项目。如果该项目在 DynamoDB 中不存在,则 DAX 会将一个空项目存储在项目缓存中,然后将该空项目返回到该应用程序。现在,假设该应用程序发送了针对同一项目的另一个 GetItem 请求。DAX 将在项目缓存中找到空项目,然后立即将它返回到该应用程序,而根本不会征求 DynamoDB 的意见。

逆向缓存条目将保留在 DAX 项目缓存中,直到其项目 TTL 已过期、LRU 已撤销或者直到使用 PutItemUpdateItemDeleteItem 修改该项目。

DAX 查询缓存将采用类似的方法处理逆向缓存结果。如果某个应用程序执行了 QueryScan,并且 DAX 查询缓存不包含缓存的结果,则 DAX 会将该请求发送到 DynamoDB。如果结果集中没有匹配的项目,则 DAX 会将空结果集存储在查询缓存中,并将空结果集返回到该应用程序。后续的 QueryScan 请求将生成相同的 (空) 结果集,直到该结果集的 TTL 已过期。

针对写入的策略

DAX 的直写行为适合很多应用程序模式。但是,也存在一些可能不适合直写模型的应用程序模式。

对于对延迟敏感的应用程序,通过 DAX 进行写入会产生一个额外的网络跃点,因此,写入到 DAX 将比直接写入到 DynamoDB 稍慢一点。如果您的应用程序对写入延迟敏感,您可通过改为直接写入到 DynamoDB 来降低延迟。(有关更多信息,请参阅绕写。)

对于写入密集型应用程序 (如执行批量数据加载的应用程序),可能不需要通过 DAX 写入所有数据,因为只有极小一部分数据曾经由这种应用程序读取过。当您通过 DAX 写入大量数据时,它必须调用其 LRU 算法在缓存中为要读取的新项目腾出空间。这将减小 DAX 作为读取缓存的有效性。

当您将某个项目写入到 DAX 时,项目缓存状态将更改以适应新项目。(例如,DAX 可能需要从项目缓存中移出旧数据以便为新项目腾出空间。)新项目将保留在项目缓存中,具体取决于缓存的 LRU 算法和缓存的 TTL 设置。只要项目保留在项目缓存中,DAX 就不会从 DynamoDB 再次读取项目。

直写

DAX 项目缓存将实施直写策略 (请参阅 DAX 如何处理写入)。当您写入某个项目时,DAX 将确保缓存的项目与 DynamoDB 中存在的项目同步。这对于需要在写入项目后立即再次读取项目的应用程序很有用。但是,如果其他应用程序直接对 DynamoDB 表进行写入,则 DAX 项目缓存中的项目将不再与 DynamoDB 保持同步。

为了说明这种情况,请考虑正在使用 ProductCatalog 表的两位用户 (Alice 和 Bob)。Alice 使用 DAX 访问该表,而 Bob 则绕过 DAX 直接在 DynamoDB 中访问该表。

  1. Alice 更新了 ProductCatalog 表中的一个项目。DAX 将该请求转发到 DynamoDB,然后更新成功了。DAX 随后将该项目写入到项目缓存,并将成功的响应返回到 Alice。从那时起,直到项目最终从缓存中移出,从 DAX 读取项目的所有用户都将看到包含 Alice 的更新的项目。

  2. 不久后,Bob 更新了 Alice 写入的同一 ProductCatalog 项目,但 Bob 是直接在 DynamoDB 中更新的。DAX 不会自动刷新其项目缓存来响应通过 DynamoDB 实现的更新,因此,DAX 用户看不到 Bob 的更新。

  3. Alice 再次从 DAX 读取了该项目。该项目位于项目缓存中,因此 DAX 会将它返回到 Alice 而无需访问 DynamoDB 表。

在这种情况下,Alice 和 Bob 将看到同一 ProductCatalog 项目的不同表示形式。在 DAX 从项目缓存中移出该项目之前或其他用户使用 DAX 再次更新同一项目之前,将一直是这种情况。

绕写

如果您的应用程序需要写入大量数据 (如批量数据加载),则可以选择绕过 DAX 并将数据直接写入到 DynamoDB。此类绕写 策略将减少写入延迟;但是,项目缓存将不会与 DynamoDB 中的数据保持同步。

如果您决定使用绕写策略,请记住,DAX 将在应用程序使用 DAX 客户端读取数据时填充其项目缓存。这可能在某些情况下很有用,因为它确保了仅缓存读取得最频繁的数据 (而不是写入得最频繁的数据)。

请考虑一个希望使用 DAX 处理 GameScores 表的用户 (Charlie)。GameScores 的分区键是 UserId,因此,Charlie 的所有分数都具有相同的 UserId

  1. Charlie 希望检索其所有分数,因此,他向 DAX 发送了一条 Query 请求。假定在之前未发出过此查询,DAX 会将此查询转发到 DynamoDB 以供处理,将结果存储在 DAX 查询缓存中,然后将结果返回到 Charlie。结果集将在查询缓存中保持可用,直到被移出。

  2. 现在,假设 Charlie 玩 Meteor Blasters 游戏并获得了高分数。Charlie 向 DynamoDB 发送了一条 UpdateItem 请求,即修改 GameScores 表中的一个项目。

  3. 最后,Charlie 决定重新运行之前的 QueryGameScores 中检索其所有数据。Charlie 没有在结果中看到他的 Meteor Blasters 的高分数。这是因为查询结果来自查询缓存,而不是项目缓存。(这两种缓存是相互独立的,因此一种缓存中的更改不会影响另一种缓存。)

DAX 不会使用来自 DynamoDB 的最新数据来刷新查询缓存中的结果集。查询缓存中的每个结果集都是截止执行 QueryScan 操作时的状态。因此,Charlie 的 Query 结果不会反映其 PutItem 操作。在 DAX 从查询缓存中移出此结果集之前,将一直是这种情况。