使用 Amazon DynamoDB 实现高性能和可扩展性

简介

在本教程中,您将创建一个餐厅点评应用程序,用户可以在该应用程序中对餐厅进行评级并留下评论。然后其他用户可以浏览这些评论来查找受欢迎的餐厅。

在本教程中,您将构建用来对餐厅进行评级和评论的核心服务。用户可以对餐厅进行评论或评级,其他用户在选择餐厅时可以浏览评论和评级。

对于此服务,您将使用 Amazon DynamoDB,DynamoDB 是 AWS 提供的 NoSQL 数据库。您将通过本教程学习如何在应用程序中使用全托管的 DynamoDB 表。首先,您将学习为什么要使用 DynamoDB。然后,您将分步创建 DynamoDB 表并在应用程序中使用该表。在本教程结束时,您应当能够在应用程序中自如使用 DynamoDB。

完成所需时间:30–45 分钟

专用数据库 - DynamoDB(34 分 13 秒)
为什么使用 DynamoDB?

DynamoDB 是全托管的 NoSQL 数据库,可在任何规模下提供快如闪电的一致性能。它具有灵活的计费模型、与基础设施即代码的紧密集成、无需人工干预的运营模型。

DynamoDB 已成为以下两类应用程序的首选数据库:

  • 大规模应用程序:DynamoDB 专为大规模应用程序而构建。它基于亚马逊工程师在为了应对全球规模而扩展 Amazon.com 零售基础设施时的学习成果。从快速发展的初创公司(如 Airbnb 和 Lyft)到大型企业(如 Capital One 和 Samsung),许多工程团队都在大规模使用 DynamoDB。DynamoDB 可以在任何规模下提供相同的一致性能,因此您无需随着数据的增长而重构数据库。
  • 无服务器应用程序:DynamoDB 深受使用 AWS Lambda 和 AWS API Gateway 等服务构建无服务器应用程序的开发人员欢迎。传统数据库很难处理 Lambda 的瞬间计算特性。DynamoDB 具有 HTTP 连接模型和 AWS Identity and Access Management (IAM) 身份验证,非常适合无服务器计算。另外,DynamoDB 还提供按使用量付费这一定价选项,非常适合无服务器世界。

尽管 DynamoDB 是大规模无服务器应用程序的首选数据库,但它几乎适用于所有的联机事务处理 (OLTP) 应用程序工作负载。DynamoDB 可以处理实体之间的关系以及复杂的筛选和排序要求。

教程内容

在本教程中,您将学习如何构建使用 Amazon DocumentDB 存储数据的服务。本教程分为五个部分。

  • 在本模块中,您将创建和准备 AWS Cloud9 环境。AWS Cloud9 是基于云的集成开发环境 (IDE)。它为您提供了可从任何位置使用的开发环境,让您在其中快速构建 AWS 应用程序。


    首先,请前往 AWS Cloud9 控制台。点击 Create environment(创建环境)以启动 AWS Cloud9 环境创建向导。

    (单击进行缩放)

    在向导的第一页,为您的环境命名和提供描述,然后点击 New EC2 Instance(新建 EC2 实例)

    (单击进行缩放)

    在下一步中,您可以配置环境设置(如环境中的实例类型、平台和网络设置)。
    本教程使用默认设置即可。

    (单击进行缩放)

    最后一步显示您的连接设置。本教程使用默认设置即可。滚动到底部并点击 Create environment(创建环境)。

    (单击进行缩放)

    预配 AWS Cloud9 环境应当需要几分钟时间。如果系统正在创建环境,则会显示以下屏幕。

    (单击进行缩放)

    几分钟后,您应当会看到 AWS Cloud9 环境。如以下屏幕截图所示,AWS Cloud9 控制台有三个区域需要了解:

    • 文件资源管理器:文件资源管理器位于 IDE 的左侧区域,其中显示您目录中的文件列表。
    • 文件编辑器:文件编辑器位于 IDE 的右上区域,您可以在其中查看和编辑在文件资源管理器中选择的文件。
    • 终端:终端位于 IDE 的右下区域,您可以在其中运行用来执行代码示例的命令。
    (单击进行缩放)

    在本教程中,您将使用 Python 与 DynamoDB 表进行交互。在 AWS Cloud9 终端运行以下命令来下载并解压缩模块代码。

    cd ~/environment
    curl -sL https://s3.amazonaws.com/aws-data-labs/restaurant-dynamodb.tar | tar -xv

    在 AWS Cloud9 终端运行以下命令来查看您的目录内容。

    ls

    在 AWS Cloud9 终端,您应当会看到两个目录:

    • scripts/:此目录包含用来操纵 DynamoDB 表(例如创建表、添加二级索引、在完成后删除表)的管理脚本。
    • application/:此目录包含用来与 DynamoDB 表中的数据进行交互的代码。该代码与应用程序中的代码相似。

    在终端执行以下命令来安装本教程中的依赖项。

    sudo pip install -r requirements.txt

    pip install boto3

    pip install ksuid


    在本模块中,您配置了一个用于开发的 AWS Cloud9 实例。在下一个模块中,您将使用 DynamoDB 规划数据模型。

  • 无论使用哪种数据库,您需要确保以符合所用数据库类型的方式来构建数据模型。不同数据库间的数据建模风格各有不同。

    使用关系数据库,可以将实体规范化为原子单元,每个实体都在单独的表中表示。然后,可以使用跨表引用来指示实体之间的关系。在对数据规范化后,可以通过编写用于提取所需数据的 SQL 查询来处理访问模式。按照这种方式,将先设计数据,然后解决访问模式。

    对于 NoSQL 数据库,情况正好相反。将首先考虑访问模式,然后设计用来处理这些访问模式的数据模型。通过专门针对您的访问模式进行设计,可以创建一个比关系数据库更高效、可扩展性更强的数据库。

    如果您之前使用的是关系型数据库,使用 DynamoDB 等 NoSQL 数据库进行数据建模感觉会有所不同。但您不应试图将关系型数据库的思维方式强加于 DynamoDB。您需要确保遵循 DynamoDB 数据建模原则。

    使用 DynamoDB 等 NoSQL 数据库对数据进行建模时,请遵循以下三个步骤:

    1. 创建实体关系图 (ERD)。ERD 列出了应用程序中的不同对象(或实体),并显示它们如何通过关系相互关联。此图可帮助您了解应用程序中的核心概念。
    2. 了解访问模式。创建 ERD 后,列出在应用程序中访问对象的各种方式。这包括所有基于读取的访问模式和基于写入的访问模式。
    3. 将数据模型设计为能够处理您的访问模式。列出访问模式后,创建用来处理这些特定访问模式的数据模型。

    使用 DynamoDB 进行 NoSQL 数据建模是一个很广泛的主题,本教程中并未涵盖所有原则。有关使用 DynamoDB 进行 NoSQL 数据建模的更多信息,请参见以下 DynamoDB 文档:使用 DynamoDB 的最佳设计和构建做法。另外,您还可以查看使用 DynamoDB 进行数据建模的其他亲身体验示例

    记住这些原则,下面我们来准备数据模型。


    创建实体关系图

    对于此服务,您将处理餐厅评论信息。用户可以在应用程序中创建账户,并对他们就餐过的餐厅发表评论。评论包含用户对餐厅的评级 (1–5) 和基于文本的摘要。该应用程序允许用户查看餐厅的摘要,例如餐厅详细信息、评级汇总和最新评论。该应用程序还允许用户按邮政编码浏览餐厅。

    请注意,在这项服务中您无需处理用户信息,因为本教程后续的 Amazon Keyspaces 一节中会专门介绍这部分内容。

    记住这一点,下面让我们看看 ERD(实体-关系图)。

    (单击进行缩放)

    ERD 非常简单,仅包含以下两个实体:Restaurants 和 ReviewsRestaurants  Reviews 之间存在一对多关系,因为一个 Restaurants 可以收到许多不同用户的 Reviews。现在您已经了解了实体和关系,可以转至下一步。

    列出应用程序访问模式

    下一步是列出应用程序中的访问模式。在这里,为了设计有效的表,一定要预先充分考虑所有的访问模式。

    最重要的访问模式是提取餐厅摘要。当用户前往某家餐厅的页面时,他们希望看到与该餐厅最相关的信息,例如餐厅详细信息、所有评级的汇总以及最近的五条评论。

    作为此访问模式的一部分,我们为两个核心实体都提供了基本的创建和读取访问模式。另请注意,对于创建评论访问模式,您希望强制执行唯一性,以确保要提交评论的用户尚未评论过给定的餐厅。这可以防止用户通过提交多个负面或正面评论来影响餐厅评级的公正性。

    最后,您有时可能会发现由于某种原因需要删除评论。原因可能在于,评论会造成不必要的恶意,或者评论是由餐厅相关人员为了夸大餐厅整体评级而给出的。因此,我们将有一个删除评论访问模式。

    您需要为应用程序总共处理六种访问模式:

    • 提取餐厅摘要
    • 创建餐厅
    • 获取餐厅
    • 创建评论
    • 获取评论
    • 删除评论

    现在您已经有了应用程序的访问模式,您将演练如何设计用来处理这些模式的表。


    在本模块中,您了解了如何使用 DynamoDB 准备数据模型。首先,您了解了 DynamoDB 中的数据建模过程与关系数据库中的数据建模过程有何不同。然后我们为餐厅评级应用程序构建了实体关系图。最后,我们列出了应用程序中的访问模式。

    在下一个模块中,您将学习如何通过设计主键和二级索引来规划数据模型。

  • 在本模块中,我们开始规划用来处理访问模式的 DynamoDB 数据模型。当我们规划此数据模型时,您将了解一些有关使用 DynamoDB 进行数据建模的关键原则。


    在上一模块中,您了解了 DynamoDB 中的数据建模过程与关系数据库中的数据建模过程不同。除了流程上的差异之外,这两种数据建模的原则也有所不同。

    要了解 DynamoDB 数据建模的原则,您应当了解有关 DynamoDB 的两个设计决策。

    首先,DynamoDB 不允许跨表连接查询。连接查询是关系数据库的一个常见功能,您可以在查询时组合多个表中的值以获得最终结果。随着数据规模扩大,连接查询功能的执行速度会越来越慢,因为它们需要从多个表中读取大量值。为了避免这些扩展问题,DynamoDB 完全删除了连接查询功能。

    其次,可用于查询数据的属性受到限制。在关系数据库中,可以在查询中使用表中的任何列。对于 DynamoDB,重点在于使用主键来访问数据。按照正确的方式对主键进行建模对于实现有效的 DynamoDB 数据模型至关重要。

    这两个设计决策有助于确保 DynamoDB 表在扩展到任何大小时都具有一致的性能。由于这两个设计决策,您应当在数据建模过程中遵循以下原则:

    • 将多个实体放入单个表中。如前所述,DynamoDB 不允许跨表连接查询。为了实现与连接查询类似的功能,您将在单个表中放置多个不同的实体类型。在此示例中,这意味着 Restaurants  Reviews 位于同一个表中。
    • 对主键使用通用名称和值。所有数据访问都是通过主键完成的。但是,由于 DynamoDB 表具有多个不同的实体,而且某些实体不具有 RestaurantName 或 RatingId 之类的主键属性,因此您无法使用相应的主键名称。相反,您可以使用 PK 和 SK 等名称,PK 用于分区键,SK 用于排序键。主键的值是专门为帮助构建访问模式而设计的。
    • 使用二级索引来处理其他访问模式。表的主键用来处理有关创建和删除单个数据项的基本访问模式。您甚至可以处理一些更高级的访问模式,这些高级模式要求您在单个请求中提取多个数据项。但是,有时您会使用与表的主键设计不相符的其他访问模式。可以使用二级索引启用其他模式。二级索引的作用类似于表的附加主键,它支持对二级访问模式进行快速访问。当您在表中插入数据项中时,DynamoDB 会使用新的主键模式将这些数据项复制到二级索引中。

    记住这些原则,下面我们来设计用于处理访问模式的主键。

    设计主键

    主键设计是 DynamoDB 数据建模中至关重要的部分。主键决定您如何识别和访问表中的数据。

    第一步是确定要使用哪种类型的主键。DynamoDB 中有两种类型的主键。第一种类型是简单主键,由下面的一个元素组成:分区键。第二种类型是复合主键,由以下两个元素组成:分区键和排序键。

    如果使用简单的主键,则只能对 DynamoDB 数据项执行单个键值操作。如果使用复合主键,则可以执行更高级的模式,例如使用查询操作检索多个具有相同分区键的数据项。

    在大多数应用程序中,您希望选择复合主键,因为它允许随着应用程序的增长,更灵活地进行更复杂的建模。我们在本教程中使用复合主键。

    选择主键类型后,需要为实体设计主键。首先要考虑的是任何唯一性要求。如果您的实体需要在特定维度上保持唯一,则在构建主键时必须考虑这个要求。

    在本教程中,我们对两个实体都有唯一性要求。首先,必须按餐厅名称对 Restaurant 进行唯一标识。其次,我们需要确保用户无法对同一家餐厅评论多次。因此,Review 数据项必须由餐厅名称和用户名的组合来唯一标识。

    记住这一点,下面我们可以对这两个实体使用以下主键结构。

    (单击进行缩放)

    对于 Restaurant 实体,PK  SK 的值都是 REST#<RestaurantName>

    对于 Review 实体,PK 的值为 USER#<Username>SK 的值为 REST#<RestaurantName>。通过将 Username 和 RestaurantName 编码到主键中,我们可以断言这两个属性的组合对于任何给定的 Review 都是唯一的。

    请注意,对于这两种实体类型,我们在主键值中包含前缀(REST# 和 USER#)。这有助于识别数据项的类型并防止实体类型之间出现冲突。

    您可以使用 NoSQL Workbench for DynamoDB 来帮助进行数据建模。此工具允许您创建表、在表中插入数据项并查看数据项在表中的示例。

    下面是加载到 NoSQL Workbench 中的一些 Restaurant 和 Review 数据项的示例。

    (单击进行缩放)

    前两个数据项针对的是 Susan's Steaks 和 Thai Time 餐厅。接下来的两个数据项是对 Thai Time 餐厅的评论。

    这个基本的主键模式已经满足了六种访问模式中的五种:

    • 创建餐厅
    • 获取餐厅
    • 创建评论
    • 获取评论
    • 删除评论

    在下一个模块中,我们将演练一些用来处理其中一些访问模式的代码。接着,让我们看一下如何处理提取餐厅摘要访问模式,这是更复杂的访问模式。

    使用非规范化和二级索引

    我们想要处理的最终访问模式更加复杂。我们希望提取餐厅摘要,其中包括:

    • Restaurant 数据项带有餐厅详细信息。
    • 餐厅所有评论的评级摘要 (1–5)。
    • 该餐厅最近五条评论的文本。

    我们将使用两种不同的策略来帮助实现这种访问模式。

    首先,用户每次查看餐厅时,都会提取该餐厅的每个 Review 数据项以重新计算该餐厅的评级汇总,这样效率很低。相反,您可以通过存储餐厅所有评级的汇总来对数据进行非规范化。每个 Restaurant 数据项都有五个属性,分别指示餐厅收到的 1 星、2 星、3 星、4 星和 5 星评级的数量。每当有新评论创建时,您的应用程序还会递增父 Restaurant 数据项上的相关计数器属性。这样,就可以快速显示平均评级、评级数量和评级分布情况。

    在 NoSQL Workbench 中,数据项现在如下所示。

    (单击进行缩放)

    请注意两个 Restaurant 数据项现在如何具有每个星级的评级数属性。

    第二种策略可帮助您在单个请求中检索 Restaurant 数据项和最近的五个 Review 数据项。为了处理这种情况,请使用二级索引来重塑数据。二级索引向表添加一个额外的主键结构,以实现额外的查询模式。

    在二级索引中,键结构如下所示。

    (单击进行缩放)

    现在为这些数据项添加两个新属性:GSI1PK 和 GSI1SK。这些属性是通用键名称,就像主键一样。

    请注意,Restaurant 和 Review 数据项的 GSI1PK 具有相同的值。当不同的数据项具有相同的分区键值时,我们称之为在同一个数据项集合中。您可以使用查询操作在单个请求中检索同一数据项集合中的多个数据项。

    每当创建一个 Review 时,应用程序都会为该评论分配一个 ReviewId,该 ID 用在 GSI1SK 值中。ReviewId 是一种 K 可排序唯一标识符 (KSUID)。KSUID 为通用唯一标识符 (UUID) 提供了某种唯一性保证,同时还包括基于时间的前缀,通过这样的前缀可以按时间顺序排序。这允许您获取提取餐厅的最新 Review 数据项。

    现在,您的基表在 NoSQL Workbench 中看上去如下所示。

    (单击进行缩放)

    请注意如何为所有数据项添加 GSI1PK 和 GSI1SK 值(以红色框出)。为了更便于查看,我们删除了在上一步中添加的评级。

    如果切换到 GSI1 二级索引视图,可以看到二级索引的数据是如何重新排列的。

    (单击进行缩放)

    请注意,Thai Time Restaurant 数据项和两个 Review 数据项如何位于同一数据项集合中(以红色框出)。

    在下一个模块中,您将学习如何检索此数据以处理访问模式。

    创建 DynamoDB 表

    在您下载的 scripts/ 目录中,有一个名为 create_table.py 的文件,该文件用来为应用程序创建 DynamoDB 表。此文件的内容如下所示。

    import boto3
    
    dynamodb = boto3.client("dynamodb")
    
    try:
        dynamodb.create_table(
            TableName="Restaurants",
            AttributeDefinitions=[
                {"AttributeName": "PK", "AttributeType": "S"},
                {"AttributeName": "SK", "AttributeType": "S"},
                {"AttributeName": "GSI1PK", "AttributeType": "S"},
                {"AttributeName": "GSI1SK", "AttributeType": "S"},
            ],
            KeySchema=[
                {"AttributeName": "PK", "KeyType": "HASH"},
                {"AttributeName": "SK", "KeyType": "RANGE"},
            ],
            GlobalSecondaryIndexes=[
                {
                    "IndexName": "GSI1",
                    "KeySchema": [
                        {"AttributeName": "GSI1PK", "KeyType": "HASH"},
                        {"AttributeName": "GSI1SK", "KeyType": "RANGE"},
                    ],
                    "Projection": {"ProjectionType": "ALL"},
                    "ProvisionedThroughput": {
                        "ReadCapacityUnits": 5,
                        "WriteCapacityUnits": 5,
                    },
                }
            ],
            ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
        )
        print("Table created successfully.")
    except Exception as e:
        print("Could not create table. Error:")
        print(e)

    该脚本使用 Boto3 与 DynamoDB 进行交互,Boto3 是适用于 Python 的 AWS SDK。该脚本创建一个名为 Restaurants 的表,该表将用于餐厅评级应用程序。它使用前面提到的通用名称(PK  SK)来指定主键元素。该脚本还使用前面讨论的通用名称创建一个名为 GSI1 的全局二级索引。最后,该脚本指定要在此表上预配的读取容量单位和写入容量单位

    在终端运行以下命令来执行该脚本并创建表。

    python scripts/create_table.py

    您应当会在终端看到一条指示表已成功创建的消息。

    接下来,将一些示例数据加载到此表中。通过运行以下命令来运行批量加载脚本。

    python scripts/bulk_load_table.py

    您应当会在终端看到一条指示数据已成功加载的消息。


    在此模块中,您规划了 DynamoDB 数据模型并加载了一些示例数据。为此,您了解了一些关键的 DynamoDB 数据建模原则。您还了解了主键和二级索引等 DynamoDB 概念,并查看了如何使用 NoSQL Workbench for DynamoDB 进行数据建模。最后,您使用 Boto3 创建了一个表并在该表中加载了一些示例数据,Boto3 是适用于 Python 的 AWS SDK。

    在下一个模块中,您将学习如何在应用程序代码中与 DynamoDB 进行交互。

  • 在本模块中,您将学习如何在应用程序中以数据库形式与 DynamoDB 进行交互。您使用在上一模块中创建的表,该表中已经加载了示例数据。

    请记住,您的餐厅应用程序中有以下六种访问模式:

    • 提取餐厅摘要
    • 创建餐厅
    • 获取餐厅
    • 创建评论
    • 获取评论
    • 删除评论

    在本模块中,您将学习如何编写用来实施其中三种访问模式的代码。让我们从一个简单的示例开始,并在我们转向其他访问模式时提高复杂性。


    创建餐厅

    要处理的第一个访问模式是创建餐厅访问模式。这是最简单的访问模式,因为它涉及到对单个数据项进行操作。在创建 Restaurant 数据项时,需要确保新创建的数据项与现有的 Restaurant 数据项不同名。

    在 application/ 目录中,有一个名为 create_restaurant.py 的文件。在文件编辑器中打开此文件。此文件的内容如下所示。

    import boto3
    
    dynamodb = boto3.client("dynamodb")
    
    
    def create_restaurant(name, cuisine, address):
        try:
            resp = dynamodb.put_item(
                TableName="Restaurants",
                Item={
                    "PK": {"S": "REST#{}".format(name)},
                    "SK": {"S": "REST#{}".format(name)},
                    "GSI1PK": {"S": "REST#{}".format(name)},
                    "GSI1SK": {"S": "REST#{}".format(name)},
                    "name": {"S": name},
                    "cuisine": {"S": cuisine},
                    "address": {"S": address},
                },
                ConditionExpression="attribute_not_exists(PK)",
            )
            return {"name": name, "cuisine": cuisine, "address": address}
        except Exception as e:
            print("Could not create restaurant")
            print(e)
            return False
    
    
    restaurant = create_restaurant(
        "Bev's Bistro", "French", "541 Salazar Ranch, South Kristen, MS 00857"
    )
    
    if restaurant:
        print("Created restaurant: {}".format(restaurant["name"]))

    在导入 Boto3 库并初始化客户端之后,有一个名为 create_restaurant 的函数。此函数与应用程序代码中的函数相似。通过传入名称、菜品和地址参数,并调用 PutItem 操作以在表中插入一个 Restaurant 数据项。

    请注意,PutItem 操作包括一个 ConditionExpression 参数。用于确认是否存在具有相同主键的数据项。如果的确存在,PutItem 操作会被拒绝,而且不会写入该数据项。

    在此文件的底部,有一个有关如何使用该函数的示例。它调用 create_restaurant 函数以创建一家名为 Bev's Bistro 的新餐厅。

    您可以通过在终端运行以下命令来执行脚本并新建餐厅。

    python application/create_restaurant.py

    您应当会在终端看到一条显示新餐厅的数据项已成功创建的消息。

    尝试再次执行该脚本。您应当会收到一条错误,指出由于条件检查失败而无法创建餐厅。这是因为在调用中包括了 ConditionExpression ,这样可防止意外覆盖现有数据项。

    添加对餐厅的评论

    接着,让我们演练一个允许用户评论餐厅的过程。回顾一下,在上一个模块中,我们在 DynamoDB 表中进行了一些非规范化,以便允许对 Restaurant 数据项的评级进行汇总。因此,在处理这种访问模式时,我们需要执行以下两个操作:

    1. 如果评论用户尚未评过此餐厅,请向表中添加一个 Review 数据项。
    2. 递增父 Restaurant 数据项的相关评级属性。

    请注意,这两个操作必须同时成功或失败。如果用户已经评论过此餐厅,您将不希望增加父 Restaurant 的评级属性。另外,如果用来递增评级属性的写入操作失败,您将不需要存储 Review 数据项,因为这会导致数据不正确。

    要处理此问题,可以使用 DynamoDB 事务。事务允许您将多个操作组合在单个请求中,并使整个请求同时成功或失败。这会显著简化对多个数据项执行操作的复杂工作流。

    在 applications/ 目录中,有一个名为 create_review.py 的文件。在文件编辑器中打开此文件。其内容应如下所示。

    import datetime
    
    import boto3
    from ksuid import ksuid
    
    dynamodb = boto3.client("dynamodb")
    
    RATINGS = {
        1: "one_stars",
        2: "two_stars",
        3: "three_stars",
        4: "four_stars",
        5: "five_stars",
    }
    
    
    def create_review(restaurant, username, rating, review_text):
        review_id = str(ksuid())
        rating_attr = RATINGS[rating]
        try:
            resp = dynamodb.transact_write_items(
                TransactItems=[
                    {
                        "Put": {
                            "TableName": "Restaurants",
                            "Item": {
                                "PK": {"S": "USER#{}".format(username)},
                                "SK": {"S": "REST#{}".format(restaurant)},
                                "GSI1PK": {"S": "REST#{}".format(restaurant)},
                                "GSI1SK": {"S": "#REVIEW#{}".format(review_id)},
                                "username": {"S": username},
                                "restaurant": {"S": restaurant},
                                "id": {"S": review_id},
                                "rating": {"N": str(rating)},
                                "review": {"S": review_text},
                                "created_at": {"S": datetime.datetime.now().isoformat()},
                            },
                            "ConditionExpression": "attribute_not_exists(PK)",
                        },
                    },
                    {
                        "Update": {
                            "TableName": "Restaurants",
                            "Key": {
                                "PK": {"S": "REST#{}".format(restaurant)},
                                "SK": {"S": "REST#{}".format(restaurant)},
                            },
                            "ConditionExpression": "attribute_exists(PK)",
                            "UpdateExpression": "SET #rating = if_not_exists(#rating, :zero) + :inc",
                            "ExpressionAttributeNames": {"#rating": rating_attr},
                            "ExpressionAttributeValues": {
                                ":inc": {"N": "1"},
                                ":zero": {"N": "0"},
                            },
                        }
                    },
                ]
            )
            print("Added review from {} to restaurant {}".format(username, restaurant))
            return True
        except Exception as e:
            print("Could not add review to restaurant")
    
    
    create_review("Bev's Bistro", "hungryhank", 5, "Great food, great price!")

    此处的结构与前面的示例相似。导入所需的库后,有一个名为 create_review 的函数,该函数与应用程序代码中的函数相似。该函数在 DynamoDB 中调用 TransactWriteItems 操作。该事务包括两个操作:

    1. PutItem 操作,用来在不存在具有相同主键的数据项时创建 Review 数据项。
    2. UpdateItem 操作,用来递增父 Restaurant 数据项的相关评级属性。

    在此文件的底部,有一个通过使用某个示例数据测试 create_review 函数的语句。在终端运行以下命令来执行 create_review.py 脚本并创建评论。

    python application/create_review.py

    您应当会看到一条指出用户 hungryhank 已为 Bev's Bistro 添加评论的消息。

    尝试再次执行该脚本。这次,您应当会收到一条指出无法添加评论的错误消息。这是因为用户 hungryhank 已经为 Bev's Bistro 添加了评论。

    提取餐厅摘要

    我们要介绍的最后一个访问模式是提取餐厅摘要访问模式。

    当用户在应用程序中前往某家餐厅的页面时,我们希望显示有关该餐厅本身的详细信息、收到的所有评级的汇总、最近五个评论的文本。

    创建评级访问模式下,您了解了如何对数据进行非规范化并将评级信息复制到 Restaurant 数据项以快速计算汇总。现在让我们看看如何在单个请求中检索 Restaurant 数据项和最近的五个 Review 数据项。

    查询操作允许您读取多个具有相同分区键的数据项。单个分区键中的数据项按照 sort-key 值进行排序。

    回顾一下,我们在 NoSQL Workbench 中使用一些 Review 数据项对 Restaurant 数据项进行建模。GSI1 二级索引的视图如下所示。

    (单击进行缩放)

    请注意,所有的 Review 数据项都排在数据集合的前部,其次是 Restaurant 数据项。而且还按 ReviewId 对 Review 数据项进行了排序。由于您使用的是 KSUID,因此 Reviews 按时间顺序排序。因此,要获取 Restaurant 和最近的五个 Review,您需要从数据项集合的末尾开始并检索六个数据项。

     applications/ 目录中,有一个名为 fetch_restaurant_summary.py 的脚本,打开该文件可在文件编辑器中查看它。其内容应如下所示。

    import boto3
    
    from entities import Restaurant, Review
    
    dynamodb = boto3.client("dynamodb")
    
    
    def fetch_restaurant_summary(restaurant_name):
        resp = dynamodb.query(
            TableName="Restaurants",
            IndexName="GSI1",
            KeyConditionExpression="GSI1PK = :gsi1pk",
            ExpressionAttributeValues={
                ":gsi1pk": {"S": "REST#{}".format(restaurant_name)},
            },
            ScanIndexForward=False,
            Limit=6,
        )
    
        restaurant = Restaurant(resp["Items"][0])
        restaurant.reviews = [Review(item) for item in resp["Items"][1:]]
    
        return restaurant
    
    
    restaurant = fetch_restaurant_summary("The Vineyard")
    
    print(restaurant)
    for review in restaurant.reviews:
        print(review)

    该设置与前两个示例相似导入所需的库后,有一个名为 fetch_restaurant_summary 的函数,该函数与应用程序中的函数相似。此函数提取餐厅名称并返回 Restaurant 数据项和该餐厅最近的五个 Review 数据项。

    该函数使用查询操作来执行上述提取和返回操作。将查询与 DynamoDB 结合使用时,您必须指定关键条件表达式来描述要检索的数据项。键条件表达式必须包含与您想要的分区键完全匹配的内容,可以包含您想要匹配的排序键的条件。

    在这个查询操作中,您将针对 GSI1 二级索引运行该操作。您需要指定确切的分区键,以查询某特定餐厅的数据。另外,还需要指定 ScanIndexForward=False 属性。这代表使用相反的顺序读取数据项集合中的数据项。这将从数据项集合的末尾开始,也就是说,先读取 Restaurant 数据项,后读取集合中最近的五个 Review 数据项。最后,存在最多六个数据项的限制,因此,您只能检索 Restaurant 数据项和最近的五个 Review 数据项。

    在此文件的底部,有一个使用餐厅名称 The Vineyard 调用该函数的示例。在终端运行以下命令来执行该脚本。

    python application/fetch_restaurant_summary.py

    您应当会在终端看到以下输出结果。

    $ python application/fetch_restaurant_summary.py
    Restaurant<The Vineyard -- Fine Dining>
    Review<The Vineyard -- markmartin (2020-05-24T11:59:44)>
    Review<The Vineyard -- kgraham (2020-05-14T15:01:52)>
    Review<The Vineyard -- ewilliams (2020-05-13T02:36:30)>
    Review<The Vineyard -- hannah21 (2020-05-04T03:44:26)>
    Review<The Vineyard -- john97 (2020-04-27T20:45:52)>

    输出结果中列显 Restaurant 数据项和 The Vineyard 餐厅最近的五个 Review 数据项。


    在本模块中,您学习了如何在应用程序代码中实施一些访问模式。首先,您了解了如何使用条件表达式来确保不会覆盖现有数据项。然后,您使用 DynamoDB 事务在单个请求中对多个数据项执行操作。最后,您使用查询操作在单个请求中检索多个异构数据项类型。

    在下一个模块中,您将学习如何清除在本教程中创建的资源。

  • 在本教程中,您创建了一个 DynamoDB 表,并将该表用作应用程序中餐厅评级服务的主数据库。DynamoDB 对于大多数 OLTP 应用程序都非常适合,尤其受大规模应用程序和无服务器应用程序的青睐。DynamoDB 在扩展时提供快速而又一致的性能,还提供灵活的计费模型和无需人工干预的运营模型。

    在本模块中,您将清除在本教程中创建的资源,以免产生额外费用。


    首先,删除 DynamoDB 表。在 scripts/ 目录中,有一个名为 delete_table.py 的脚本。该目录包含一个借助于 Boto3 删除表的 Python 脚本。

    要执行该脚本并删除表,请在终端运行以下命令。

    python scripts/delete_table.py

    您应当会在终端看到一个指示表已成功删除的输出结果。

    另外,您还需要删除 AWS Cloud9 开发环境。为此,请前往 AWS Cloud9 控制台。选择您为本教程创建的环境,然后点击 Delete(删除)。

    (单击进行缩放)

    在本模块中,您学习了如何清除在本教程中创建的 DocumentDB 表和 AWS Cloud9 环境。

在本教程中,您了解了如何准备使用 DynamoDB 进行数据建模、如何规划数据模型、如何在应用程序代码中实施数据模型。您应当能够在应用程序中自如使用 DynamoDB。

此页内容对您是否有帮助?