有时,我在 Redis 中“思考”。这有点难以描述,但我倾向于从如何在 Redis 中解决问题这一角度来思考现实生活中的问题(我想... 这可能是一种病态)。我在过去几年来一直在装修我的房子,其中一项最出乎意料的挑战在于处理抹灰、喷漆、应用粘合剂以及类似物品需要花费多长时间才能干燥固化。有些东西需要数周才能固化,然后再进行下一步操作。这个周末我遇到了一件小烦恼/小灾难,我告诉自己我不会再让它发生。因此... 到 Redis 中进行一次应用程序快速运行。
在这篇博文中,我将构建一个小应用程序,该应用程序使用 Node.js 和 Redis Enterprise Cloud 来跟踪事物何时“干燥”或固化。你可以记录你何时应用“喷漆”(我们将其仅用作一种广义的“材料”,但它可以是任何东西),如果它未干燥,它将不允许你再次喷漆。最后,你还可以通过查看油漆涂层已剩多长时间来检查其状态。我知道,这很优雅,但经过了过度设计——我马上就会有风险投资资金!
首先你要创建一个小型 Redis 实例。Redis Enterprise Cloud 非常适合于此目的,只要你使用它就可获得一个免费的 30 MB 实例。 我建议阅读 Redis Enterprise Cloud 快速设置——它将详细指导你完成该流程。我估计每层油漆及其在 Redis 中关联的所有键将占用约 300 个字节。因此,免费实例可以轻松处理大约 10 万个房间——足够大,足以容纳我的房子了。
注册后,你应该拥有一个端口、一个主机和一个密码。你可以使用这些信息来创建一个小型 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 操作。我们可以通过 SADD(集合添加)和 SET 的 Redis 命令来完成此操作。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 块中使用 SISMEMBER 和 TTL 再次实现此目的。我们使用上面提到的键模式,并且参数很简单
SISMEMBER 将为非集成员返回 0,如果它是该集的成员,则返回 1。TTL 在返回的值方面稍微复杂一点。如果有失效时间,则为 0 或更大。如果该键没有 TTL 但该键存在,则为 -1;如果该键不存在,则为 -2。在这种情况下,我们真的不应该有 -1,因此我们只要看是否为 0 或更大即可。因此,我们仍然可以有 2 个布尔值
返回 | 返回 | 返回 | 返回 | |
SISMEMBER | 0(假) | 0(假) | 1(真) | 1(真) |
TTL | >=0(真) | <0(假) | >=0(真) | <0(假) |
结果 | 不可能的状态 | 房间不存在 | 仍需要晾干 | 可以粉刷房间了 |
为了实现这一切,我们将使用 yargs,并定义每个操作作为 yargs 模块的命令。每个命令都采用一个对象,在该对象中我们将定义命令语法和 desc(定义帮助文本),最后,命令实际执行发生在 handler 函数中。在 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 的有用的实用程序。现在,你可能会认为作为命令行实用程序的实用性是有限的,但这里相同的代码基可以毫不费力地移植到 Web 框架中,例如 Express,你也可以开始关注油漆晾干的服务。