管道和事务
了解如何使用 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,
},
})