在本模块中,您将了解如何设计在应用程序中使用的数据模型。

使用 Redis 这类内存中数据存储时,您注重的是速度和吞吐量。您希望尽快处理大量请求。为此,您根据使用案例的特定需求来组织数据。应该将数据整理为能够尽快满足您的需求,而不是像在关系数据库中那样,将数据设计为可以灵活查询的形式。使用 Redis 设计数据模型时,首先需要考虑的是您的需求和访问模式。

对于排行榜应用程序,您希望快速访问全球最高分。您有三种主要数据访问模式:

  1. 获取所有时段的最高分;
  2. 获取最近一个月的最高分;
  3. 获取给定日期的最高分。

您可以使用 Redis 有序集合来处理这些访问模式。有序集合是一种可以快速、有序地查找值的数据结构。您可以将特定用户的最高分保存在有序集合中,然后快速检索有序集合中排名前 5 的分数。

在应用程序中,为需要的每种可能的访问模式都按总体、按周和按日创建一个有序集合。出现新的高分时,将其加载到相关的有序集合中。当用户请求排名靠前的分数时,您可以从合适的有序集合中查询最高分。

在接下来的步骤中,将样本数据加载到 ElastiCache Redis 实例中,并查询数据中排名靠前的分数。

完成模块所需时间:20 分钟


  • 第 1 步:将样本数据加载到 Redis 实例中

    首先,将样本数据加载到 Redis 实例中。您将使用在上一个模块中加载到 Amazon Aurora Serverless 实例中的 300 个游戏。

    对于加载的每个游戏,您都希望将其放入三个不同的有序集合:

    1. 总排行榜;
    2. 游戏每月排行榜;和
    3. 游戏每日排行榜。

    Redis 中的这些独立对象可实现更高效的读取查询。

    scripts/ 目录中,有一个名为 loadRedis.js 的文件。其内容如下:

    const redis = require('redis')
    const fs = require('fs');
    const path = require('path');
    
    const raw = fs.readFileSync(path.resolve( __dirname, 'games.json'));
    const games = JSON.parse(raw)
    
    const client = redis.createClient({
      url: `redis://${process.env.REDIS_ENDPOINT}`
    })
    client.on('error', function(err) {
      console.log('Received Redis error:', err)
    })
    
    games.forEach((game) => {
      const gametime = new Date(game.gamedate)
      const key = `${game.username}|${game.gamedate}|${game.level}`
      client.multi()
        .zadd('Overall Leaderboard', game.score, key)
        .zadd(`Monthly Leaderboard|${gametime.getUTCMonth()}-${gametime.getUTCFullYear()}`, game.score, key)
        .zadd(`Daily Leaderboard|${gametime.getUTCDay()}-${gametime.getUTCMonth()}-${gametime.getUTCFullYear()}`, game.score, key)
        .exec((err) => {
          if (err) {
            console.log('Error: ', err);
          }
        })
    })
    
    console.log('Loaded data!')
    
    client.quit()
    

    如同 pingRedis.js 脚本一样,您正在创建 Redis 客户端。然后,将所有游戏数据读取到内存。对于每个游戏元素,您都需将其写入 Redis 三次:一次写入总排行榜、一次写入每周排行榜、一次写入每日排行榜。每次写入操作都在 Redis 中使用 ZADD 命令,将元素添加到有序集合中。

    请注意整体有序集合键名以及有序集合中元素名的结构。

    对于有序集合键名,总排行榜就是 Overall Leaderboard。对于每月和每日排行榜,则会包含日期信息。因此,2019 年 11 月的每月排行榜键名是 Monthly Leaderboard|11-2019,2019 年 11 月 8 日的每日排行榜键名是 Daily Leaderboard|8-10-2019。这种命名 scheme 让您能够在读取时时发现合适的排行榜键。

    现在,查看元素插入到表中时的键名。它由用户名、时间戳和关卡组成。将所有这些信息编码到键名中,这样,应用程序在获取总体高分时会显示有关高分的其他相关信息。

    使用以下命令执行 loadRedis.js 脚本:

    node scripts/loadRedis.js

    您应该能在终端中看到以下输出:

    Loaded data!

    在下一步中,您将学习如何从键中读取最高分。

  • 第 2 步:从有序集合读取排名靠前的分数

    现在,您已经将部分样本数据加载到表中,可以从表中读取一些数据。

    要查询有序集合中排名靠前的元素,可以在 Redis 中使用 ZREVRANGE 命令。您指定要查询的有序集合的名称、要返回到有序集合中的位置以及是否想要返回分数。

    scripts/ 目录中,有一个名为 getTopOverallScores.js 的文件。该文件的内容如下:

    const redis = require('redis')
    const _ = require('lodash')
    const client = redis.createClient({
      url: `redis://${process.env.REDIS_ENDPOINT}`
    })
    
    client.on('error', function(err) {
      console.log('Received Redis error:', err)
    })
    
    const parseKey = (key) => {
      const parts = key.split('|')
      return {
        username: parts[0],
        gamedate: parts[1],
        level: parts[2]
      }
    }
    
    const parseZRevRangeResponse = (resp) => {
      const result = _.chunk(resp, 2).map(([key, score]) => {
        const obj = parseKey(key)
        return {
          ...obj,
          score
        }
      })
      return result
    }
    
    client.zrevrange('Overall Leaderboard', '0', '4', 'WITHSCORES', (err, resp) => {
      if (err) {
        console.log('Error reading leaderboard: ', err);
      } else {
        const scores = parseZRevRangeResponse(resp)
        console.log('Top overall scores:')
        console.log(scores)
      }
    })
    
    client.quit()

    该脚本运行 ZREVRANGE 命令,获取总排行榜中排名前五的分数。

    请注意对 ZREVRANGE 命令的回调中的代码。Redis 中的结果以数组形式返回。数组首先有一个键元素、然后是一个分数元素,再然后是下一个键元素,以此类推。这不是将其返回前端最有用的方法,因此 parseZRevRangeResponse 有助于将其解析为更有意义的内容。

    使用以下命令执行脚本:

    node scripts/getTopOverallScores.js

    您应该能在终端中看到以下输出:

    Top overall scores:
    [ { username: 'debbieschneider',
        gamedate: '2019-11-09T18:41:27',
        level: '28',
        score: '9895' },
      { username: 'alicia39',
        gamedate: '2019-11-09T10:39:59',
        level: '47',
        score: '9824' },
      { username: 'rosecolleen',
        gamedate: '2019-11-10T07:09:51',
        level: '58',
        score: '9765' },
      { username: 'allisonsandra',
        gamedate: '2019-11-07T22:43:32',
        level: '62',
        score: '9760' },
      { username: 'kathrynmorris',
        gamedate: '2019-11-05T04:31:37',
        level: '85',
        score: '9722' } ]
    

    脚本应该将排在前五的分数打印为具有用户名、游戏日期、关卡和分数属性的对象。

在本模块中,您学习了如何在 ElastiCache 中对数据进行建模,以进行快速查找。然后,将样本数据加载到多个有序集合中,以满足使用案例的需求。最后,您了解了如何从有序集合中读取排名靠前的项。

在下一个模块中,您将配置 Amazon Cognito,以向应用程序添加身份验证。