学习

示例应用程序概述

Simon Prickett
作者
Simon Prickett, Redis 首席开发者倡导者

在本课程中,我们将结合一个示例应用程序,了解如何使用 Redis 作为数据存储和缓存。想象一下,我们正在构建一个社交网络应用程序,用户可以在不同的地点“签到”,并给这些地点打星级评分……从 0 分(糟糕的体验)到 5 分(有史以来最好的时光)!

在设计应用程序时,我们确定需要管理关于三个主要实体的数据

  • 用户
  • 地点
  • 签到

让我们看看我们存储的关于这些实体的数据。由于我们将 Redis 作为唯一的数据存储,我们还将考虑如何将它们映射到 Redis 数据类型……

用户#

我们将把每个用户表示为一个不包含嵌套对象的键值对平面映射。正如稍后将看到的那样,这很好地映射到了 Redis 哈希。以下是我们用于表示每个用户的模式的 JSON 表示形式

{
  "id": 99,
  "firstName": "Isabella",
  "lastName": "Pedersen",
  "email": "[email protected]",
  "password": "xxxxxx1",
  "numCheckins": 8073,
  "lastCheckin": 1544372326893,
  "lastSeenAt": 138
}

我们为每个用户分配了一个 ID,并存储了他们的基本信息。此外,在将示例数据加载到 Redis 中时,我们将使用 bcrypt 加密他们的密码。

对于每个用户,我们将跟踪他们向系统提交的签到总数,以及他们最近一次签到的时间戳和地点 ID,以便我们知道他们最后一次使用系统的时间和地点。

地点#

对于用户可以签到的每个地点,我们将维护两种类型的数据。第一种也是键值对的平面映射,包含关于该地点的摘要信息

{
  "id": 138,
  "name": "Stacey's Country Bakehouse",
  "category": "restaurant",
  "location": "-122.195447,37.774636",
  "numCheckins": 170,
  "numStars": 724,
  "averageStars": 4
}

我们为每个地点分配了一个 ID 和一个类别——稍后我们将使用该类别按类型搜索地点。“location”字段以经度、纬度格式存储坐标……这与通常的纬度、经度格式相反。稍后在查看 Redis Search 时,我们将看到如何使用它进行地理空间搜索。

对于每个地点,我们还存储了所有用户在该地点记录的签到总数、这些签到给该地点的总星数,以及该地点每次签到的平均星级评分。

我们希望为每个地点维护的第二种类型的数据是我们称之为“地点详情”的数据。这些数据以更结构化的 JSON 文档形式存在,包含嵌套对象和数组。以下是地点 138,Stacey's Country Bakehouse 的一个示例

{
  "id": 138,
  "hours": [
    { "day": "Monday", "hours": "8-7" },
    { "day": "Tuesday", "hours": "9-7" },
    { "day": "Wednesday", "hours": "6-8" },
    { "day": "Thursday", "hours": "6-6" },
    { "day": "Friday", "hours": "9-5" },
    { "day": "Saturday", "hours": "8-9" },
    { "day": "Sunday", "hours": "7-7" }
  ],
  "socials": [
    {
      "instagram": "staceyscountrybakehouse",
      "facebook": "staceyscountrybakehouse",
      "twitter": "staceyscountrybakehouse"
    }
  ],
  "website": "www.staceyscountrybakehouse.com",
  "description": "Lorem ipsum....",
  "phone": "(316) 157-8620"
}

我们希望构建一个 API,允许我们检索所有或部分这些额外详细信息,并保持文档的整体结构完整。为此,我们需要支持 JSON 的 Redis,这将在稍后看到。

签到#

签到与用户和地点不同,它们不是我们需要永久存储的实体。在我们的应用程序中,签到包含用户 ID、地点 ID、星级评分和时间戳——我们将使用这些值来更新用户和地点的属性。

每个签到可以被视为一个键值对平面映射,例如

{
  "userId": 789,
  "locationId": 171,
  "starRating": 5
}

在这里,我们看到用户 789 访问了地点 171(“Hair by Parvinder”),并且对服务非常满意。

我们需要一种方法来存储签到足够长的时间以便处理它们,但不是永久存储。我们还需要为每个签到关联一个时间戳,因为在处理数据时需要用到它。

Redis 提供了一种非常适合此用途的 Stream 数据类型——使用 Redis Streams,我们可以存储键值对的映射,并让 Redis 服务器为我们打上时间戳。Streams 也非常适合我们想要对这些数据进行的异步处理。当用户向我们的 API 发布新的签到时,我们希望尽快存储该数据并响应用户已收到。稍后,系统中的一个或多个其他部分可以对其进行进一步处理。此类处理可能包括更新用户的签到总数和最后访问字段,或者计算该地点新的平均星级评分。

应用程序架构#

我们决定使用 Node.js 以及 Express 框架和 ioredis 客户端来构建应用程序。该应用程序没有采用单体代码库,而是被拆分为四个组件或服务。它们是

  • 认证服务:监听一个 HTTP 端口,并使用 Redis 作为其他服务可以访问的共享会话存储来处理用户认证。
  • 签到接收器:监听一个 HTTP 端口,并接收来自用户的 HTTP POST 请求作为签到。每个签到都被放入一个 Redis Stream 中,以便后续处理。
  • 签到处理器:监控 Redis 中的签到 Stream,并在处理每个签到时更新用户和地点信息。
  • API 服务器:实现应用程序的大部分 API 端点,包括从 Redis 检索用户和地点信息的端点。

这些组件的组合方式如下所示

还有一个数据加载器组件,我们将用它向系统中加载一些初始示例数据。

随着课程的进展,我们将依次查看这些组件。在下一个模块中,您将进行实践操作,克隆应用程序仓库,使用 Docker 启动 Redis 服务器,并加载示例数据。

外部资源#