dot Redis 8 来了——而且是开源的

了解更多

如何使用 Redis Cloud 和 Node.js 创建一个简单的应用程序

有时,我会在 Redis 中“思考”。 有点难以描述,但我倾向于根据如何在 Redis 中解决实际问题来思考(我……认为这可能是一种疾病)。 我装修房子已经好几年了,最意想不到的挑战之一是处理石膏、油漆、涂抹粘合剂等材料干燥和固化的时间。 有些东西需要几周的时间才能固化,然后才能进行下一步。 这个周末我遇到了一件小麻烦/灾难,我告诉自己我不会让它再次发生。 所以……前往 Redis 进行应用程序速通。

在这篇文章中,我将构建一个小应用程序,它使用 Node.js 和 Redis Enterprise Cloud 来跟踪物品何时“干燥”或固化。 您可以记录您何时涂了“油漆”(我们只是将其用作广义的“材料”,但它可以是任何东西),如果它没有干,它不会让您再次涂漆。 最后,您还可以通过找出剩余的时间来检查涂层的状态。 我知道,它很优雅,但又过度设计了——我很快就会获得风险投资!

首先,获取一个小 Redis 实例。 Redis Enterprise Cloud 非常适合此用途,因为它们为您提供一个免费的 30 mb 实例,只要您使用它。我建议阅读 Redis Enterprise Cloud 快速设置——它将详细引导您完成整个过程。 我估计每层油漆以及 Redis 中与其关联的所有键将占用大约 300 字节。 因此,免费实例可以轻松处理大约 100,000 个房间 - 对于我的房子来说足够大了。

注册后,您应该有一个端口、一个主机和一个密码。 您可以使用这些来创建一个小的 JSON 文件。 我们将使用它来指示 Node.js 脚本您的凭据和主机名。 请记住,所有这些都是私密的,所以不要将其放在您的项目文件夹中(太容易意外地将其上传到 github 或其他地方)。 以下是 JSON 文件应如何结构化的示例

{
  "port": 1234,

  "password": "yourrealysecurepassword",

  "host": "host.name.of.your.redis.cloud.instance"
}

现在,让我们创建一个新目录,初始化 npm,并加载一些依赖项。

$ mkdir dryyet
$ cd ./dryyet
$ npm init
/*(Answer the `npm init` questions to your liking)*/
$  npm install redis yargs parse-duration

让我们回顾一下这三个依赖项

在我们深入探讨之前,让我们看一下我们将要执行的理论。 为了实现上述功能,我们只需要几个命令。 我们有两个应用程序操作 – paint 来涂一层油漆和 readytopaint 来检查您是否可以涂漆。 这两个操作都需要以依赖于序列的方式一起使用两个命令,我们将使用 MULTI/EXEC 块。

让我们首先看一下 paint 操作。 我们可以使用 Redis 命令 SADD(集合添加)和 SET 来实现此操作。 SADD 将长期跟踪房间是否存在(稍后会详细介绍),并启用多个用户。 我确实警告过你这是过度设计的。 集合数据类型键的键结构类似于 rooms:userid,在这种情况下,userid 可以是任何内容。 成员是您正在绘画的房间的名称。 第二个操作是一个 SET 命令,但带有一些不寻常的参数。 键的结构包含 userid 和房间,看起来像 paint:userid:room。 第一个参数是值 - 这里我们实际上并不关心该值(我们只是将其设置为“p”作为占位符) - 所有数据都保存在键本身中。 接下来的两个参数是 EX 和油漆干燥所需的时间(以秒为单位)。 最后,我们有参数 NX。 最后三个值使其工作 – 从结尾开始,NX 参数表示“仅当它是一个全新的键时才执行此命令。” EX 和干燥时间实际上是 TTL 值,这意味着该键将持续存在直到油漆干燥,然后该键将蒸发,就像油漆溶剂一样。

如果该项目已添加到集合中,则 SADD 操作将返回 1,如果该项目已存在于集合中,则返回 0。 如果它设置了该键,则 SET … EX … NX 将返回“OK”,如果该操作无法发生,因为该键已存在,则返回 null/(nil)。 这样做的好处是,您有两个操作可以归结为两个布尔值。 让我们在这里映射逻辑

  返回 返回 返回 返回
SADD 0(假) 0(假) 1(真) 1(真)
SET … EX … NX OK(真) null/nil(假) OK(真) null/nil(假)
结果 允许重新粉刷 不允许重新粉刷 允许重新粉刷(这是第一层) 不可能的状态

另一个操作是检查房间是否可以粉刷。 我们可以再次在 MULTI/EXEC 块中使用 SISMEMBERTTL 来实现此目的。 我们使用如上所述的键模式,并且参数是直接的

  • SISMEMBER 具有键和房间,
  • TTL 只是键。

SISMEMBER 将返回 0 表示不是集合的成员,如果是集合的成员则返回 1。 另一方面,就返回值而言,TTL 有点复杂。 如果存在生存时间,则它将为 0 或更高。 如果该键没有 TTL 但该键存在,则它将为 -1,如果该键不存在,则为 -2。 在这种情况下,我们实际上不应该有 -1,所以我们只需查看它是否为 0 或更高。 因此,我们仍然可以有两个布尔值

  返回 返回 返回 返回
SISMEMBER 0(假) 0(假) 1(真) 1(真)
TTL >=0(真) <0(假) >=0(真) <0(假)
结果 不可能的状态 房间不存在 仍然需要干燥 可以重新粉刷房间

为了方便所有这些,我们将使用 yargs 并将每个操作定义为 yargs 模块的命令。 每个命令都接受一个对象,我们将在其中定义命令语法和 desc(定义帮助文本),最后,命令的实际执行发生在处理程序函数中。 在 Redis 命令执行并关闭 redis 连接后,该进程才能终止。

const
  redis = require('redis'),
  parseDuration = require('parse-duration');
let
  client;


function createConnection(argv) {
  client = redis.createClient(require(argv.connection));
}

require('yargs')
  .option('connection', {
    describe: 'JSON File with Redis connection options',
    demandOption: true,
  })
  .command({
    command: 'paint <userid> <room> <duration>',
    desc: 'Add a coat of paint',
    handler: (argv) => {
      createConnection(argv);
      let duration = parseDuration(argv.duration);
      let durationSeconds = duration > 999 ? Math.round(duration / 1000) : 1;
      client.multi()
        .sadd(
          `rooms:${argv.userid}`,
          argv.room
        )
        .set(
          `paint:${argv.userid}:${argv.room}`,
          'p',
          'EX',
          durationSeconds,
          'NX'
        )
        .exec((err, responses) => {
          if (err) { throw err; }
          let newRoom = Boolean(responses[0]);
          let ttlSet = responses[1] === 'OK' ? true : false;
          console.log(`Hi ${argv.userid}.`);
          if (newRoom) {
            if (ttlSet) {
              console.log(`The first coat for room ${argv.room} will be dry in ${durationSeconds} second(s).`);
            } else {
              console.log('This is impossible.');
            }
          } else {
            if (ttlSet) {
              console.log(`You can repaint ${argv.room} and it will be dry in ${durationSeconds} second(s).`);
            } else {
              console.log(`The room ${argv.room} was recently painted and you shouldn't repaint.`);
            }
          }
          client.quit();
        });
    }
  })
  .command({
    command: 'readytopaint <userid> <room>',
    desc: 'Check if you are ready to paint again',
    handler: (argv) => {
      createConnection(argv);
      client.multi()
        .sismember(`rooms:${argv.userid}`, argv.room)
        .ttl(`paint:${argv.userid}:${argv.room}`)
        .exec((err, responses) => {
          if (err) { throw err; }
          console.log(`Hi ${argv.userid}.`);
          let roomExists = Boolean(responses[0]);
          let ttlResponse = responses[1];
          let timeLeftToLive = ttlResponse >= 0;
          if (timeLeftToLive) {
            if (roomExists) {
              console.log(`The room ${argv.room} still needs to dry. It will be ready to re-coat in ${ttlResponse} second(s)`);
            } else {
              console.log('This is invalid.');
            }
          } else {
            if (roomExists) {
              console.log(`The room ${argv.room} can be repainted.`);
            } else {
              console.log(`${argv.room} doesn't exist.`);
            }
          }
          client.quit();
        });
    }
  })
  .demandCommand()
  .help()
  .argv;

最后,您有 demandCommand(),它触发命令的要求,如果它不存在则会出错,并且 help() 负责在需要时呈现帮助文本。 最后,您以 .argv 结束,这将使所有这些都处于运行状态。

要运行该应用程序,您可以使用以下命令粉刷房间

$ node index.js paint kyle bedroom 60minutes --connection ~/path-to-your-file.json

如果您想检查一个房间是否可以粉刷,请运行命令

$ node index.js readytopaint kyle bedroom --connection ~/path-to-your-file.json

就是这样! 在不到 100 行的代码中,我们有一个有用的小实用程序,它使用 Node.js 和 Redis Enterprise Cloud。 现在,您可能会认为作为命令行实用程序,它的用途是有限的,但是这里的基础可以轻松地移植到像 Express 这样的 Web 框架,您也可以启动观看油漆干燥的服务。