Redis批量操作:管道和脚本

Tags
Redis
Lua
数据传输协议
数据库事务
CreatedTime
Aug 24, 2022 03:34 PM
Slug
UpdatedTime
Last updated August 24, 2022

管道

什么是管道(Pipeline)?

通过管道可以一次性发送多条命令并在执行完后一次性将结果返回,当一组命令中每条命令都不依赖于之前命令的执行结果时就可以将这组命令一起通过管道发出。管道通过减少客户端与 Redis 的通信次数来实现降低往返时延累计值的目的。
notion image
不使用管道时的命令执行示意图(纵向表示时间)(如上图)
notion image
使用管道时的命令执行示意图

深入理解:为什么 pipeline 会提速?

一个完整的交互流程如下:
  1. 客户端进程调用 write()把消息写入到操作系统内核为 Socket 分配的 send buffer 中
  1. 操作系统会把 send buffer 中的内容写入网卡,网卡再通过网关路由把内容发送到服务器端的网卡
  1. 服务端网卡会把接收到的消息写入操作系统为 Socket 分配的 recv buffer
  1. 服务器进程调用 read()读取消息然后进行处理
  1. 处理完成后调用 write()把返回结果写入到服务器端的 send buffer
  1. 服务器操作系统再将 send buffer 中的内容写入网卡,然后发送到客户端
  1. 客户端操作系统将网卡内容读到 recv buffer 中
  1. 客户端进程调用 read()从 recv buffer 中读取消息并返回
这其中除了网络开销,花费时间最长的就是进行系统调用 write()和 read()了,这一过程需要操作系统由用户态切换到内核态,中间涉及到的上下文切换会浪费很多时间
使用管道时,多个命令只会进行一次 read()和 wrtie()系统调用,因此使用管道会提升 Redis 服务器处理命令的速度,随着管道中命令的增多,服务器每秒处理请求的数量会线性增长,最后会趋近于不使用管道的 10 倍。

代码使用

About pipeline():The commands are queued in memory and flushed to Redis by calling the exec method
关于 pipeline() 方法:这个方法将命令放入内存队列中,当调用exec时,他们会传给 redis 并且执行。
const Redis = require("ioredis"); const redis = new Redis(); // 链式调用 redis .pipeline() .set("foo", "bar") .del("cc") .exec((err, results) => {});

注意事项

在 Redis 中,如果客户端使用管道发送了多条命令,那么服务器就会将多条命令放入一个队列中(这里会消耗内存进行存储)。
所以管道中命令的数量并不是越大越好(太大容易撑爆内存),而是应该有一个合理的值。

脚本

介绍

redis 的脚本经常和管道被一同提起,相较于管道,脚本的速度更快,并且脚本中无需担心“竞态条件”。
脚本的优势:
  • 减少网络开销。原因和管道一样。
  • 原子操作:Redis 会将整个脚本作为一个整体执行,中间不会被其他命令插入。**换句话说在编写脚本的过程中无需担心会出现竞态条件,也就无需使用事务。事务可以完成的所有功能都可以用脚本来实现。
  • 代码复用:客户端发送的脚本会永久存储在 Redis 中,**这就意味着其他客户端(可以是其他语言开发的项目)可以复用这一脚本而不需要使用代码完成同样的逻辑。

使用

在 redis 中使用脚本需要借助 Lua 语言。
执行脚本语法:EVAL script numkeys key [key ...] arg [arg ...]
举例:EVAL "redis.call('SET', KEYS[1], ARGV[1]);redis.call('EXPIRE', KEYS[1], ARGV[2]); return 1;" 1 userAge 10 60
ioredis使用DEMO:
const Redis = require('ioredis') const redis = new Redis() const luaContent = `redis.call('SET', KEYS[1], ARGV[1]); redis.call('EXPIRE', KEYS[1], ARGV[2]); return 1; ` // 定义命令 redis.defineCommand('cmd1', { numberOfKeys: 1, lua: luaContent }) // 执行命令 redis.cmd1('userAge', 10, 60, (err, result) => { if (err) { console.log(err) return; } console.log({ result }) })

参考链接