管道和事务

了解如何使用 Redis 管道和事务

Redis 允许您将一系列命令批量发送到服务器。您可以使用两种类型的批量操作:

  • 管道 通过在一次通信中将多个命令一起发送到服务器,从而避免网络和处理开销。然后服务器将所有响应在一个单独的通信中发回。有关更多信息,请参阅Pipelining页面。
  • 事务 保证所有包含的命令将完全执行,而不会被其他客户端的命令中断。有关更多信息,请参阅Transactions页面。

执行管道

有两种方法可以在管道中执行命令。首先,node-redis 将自动对在事件循环的同一个“tick”内执行的命令进行管道处理。通过将它们包含在Promise.all() 调用中,您可以非常轻松地确保命令在同一个 tick 内发生,如下例所示。链式调用的 then(...) 回调是可选的,对于写入数据且仅返回状态结果的命令,通常可以省略它。

await Promise.all([
  client.set('seat:0', '#0'),
  client.set('seat:1', '#1'),
  client.set('seat:2', '#2'),
]).then((results) =>{
    console.log(results);
    // >>> ['OK', 'OK', 'OK']
});

await Promise.all([
    client.get('seat:0'),
    client.get('seat:1'),
    client.get('seat:2'),
]).then((results) =>{
    console.log(results);
    // >>> ['#0', '#1', '#2']
});

您还可以使用 multi() 方法创建一个管道对象,然后使用类似于标准命令方法(例如 set()get())的方法向其添加命令。这些命令被缓冲在管道中,只有当您在管道对象上调用 execAsPipeline() 方法时才会执行。同样,then(...) 回调是可选的。

await client.multi()
    .set('seat:3', '#3')
    .set('seat:4', '#4')
    .set('seat:5', '#5')
    .execAsPipeline()
    .then((results) => {
        console.log(results);
        // >>> ['OK', 'OK', 'OK']
    });

这两种方法几乎等效,但在管道执行期间连接丢失时,它们的行为有所不同。连接重新建立后,Promise.all() 管道将从中断发生的地方继续执行,而 multi() 管道将丢弃所有未执行的剩余命令。

执行事务

事务的工作方式与管道类似。使用 multi() 命令创建一个事务对象,在该对象上调用命令方法,然后调用事务对象的 exec() 方法来执行它。

const [res1, res2, res3] = await client.multi()
    .incrBy("counter:1", 1)
    .incrBy("counter:2", 2)
    .incrBy("counter:3", 3)
    .exec();

console.log(res1); // >>> 1
console.log(res2); // >>> 2
console.log(res3); // >>> 3

监视键的更改

Redis 支持乐观锁,以避免对不同键进行不一致的更新。基本思想是在处理更新时监视事务中使用的任何键的更改。如果监视的键确实发生了更改,您必须使用这些键的最新数据重新开始更新。有关乐观锁的更多信息,请参阅Transactions

以下代码读取一个表示命令 shell PATH 变量的字符串,然后在尝试写回之前向该字符串附加一个新的命令路径。如果在写入之前被其他客户端修改了监视的键,则事务将中止。请注意,您应该在常规 client 对象上同步调用监视键的只读命令,但仍需在用 multi() 创建的事务对象上调用事务命令。

对于生产环境的使用,您通常会在循环中调用类似以下的代码,直到它成功或报告/记录失败。

// Set initial value of `shellpath`.
client.set('shellpath', '/usr/syscmds/');

// Watch the key we are about to update.
await client.watch('shellpath');

const currentPath = await client.get('shellpath');
const newPath = currentPath + ':/usr/mycmds/';

// Attempt to write the watched key.
await client.multi()    
    .set('shellpath', newPath)
    .exec()
    .then((result) => {
        // This is called when the pipeline executes
        // successfully.
        console.log(result);
    }, (err) => {
        // This is called when a watched key was changed.
        // Handle the error here.
        console.log(err);
    });

const updatedPath = await client.get('shellpath');
console.log(updatedPath);
// >>> /usr/syscmds/:/usr/mycmds/

在多个并发请求共享一个连接(例如 Web 服务器)的环境中,您必须将上述事务逻辑包装在对 client.executeIsolated 的调用中以获取一个独立的连接,如下所示

await client.executeIsolated(async (client) => {
   await client.watch('shellpath');
   // ...
})

这一点很重要,因为服务器按连接跟踪 WATCH 的状态,同一连接上的并发 WATCH 和 MULTI/EXEC 调用会相互干扰。

您可以在调用 createClient 时配置隔离连接池的大小

const client = createClient({
    isolationPoolOptions: {
        min: 1,
        max: 100,
    },
})
为此页面评分
返回顶部 ↑