腾讯二面: Redis 如何使用批量操作提高效率?原创
一个 Redis 命令的执行可以简化为以下 4 步:
-
发送命令 -
命令排队 -
命令执行 -
返回结果
其中,第 1 步和第 4 步耗费时间之和称为 Round Trip Time (RTT,往返时间) ,也就是数据在网络上传输的时间。
使用批量操作可以减少网络传输次数,进而有效减小网络开销,大幅减少 RTT。
原生批量操作命令
Redis 中有一些原生支持批量操作的命令,比如:
-
mget
(获取一个或多个指定 key 的值)、mset
(设置一个或多个指定 key 的值)、 -
hmget
(获取指定哈希表中一个或者多个指定字段的值)、hmset
(同时将一个或多个 field-value 对设置到指定哈希表中)、 -
sadd
(向指定集合添加一个或多个元素) -
......
不过,在 Redis 官方提供的分片集群解决方案 Redis Cluster 下,使用这些原生批量操作命令可能会存在一些小问题需要解决。就比如说 mget
无法保证所有的 key 都在同一个 hash slot(哈希槽)上,mget
可能还是需要多次网络传输,原子操作也无法保证了。不过,相较于非批量操作,还是可以节省不少网络传输次数。
整个步骤的简化版如下(通常由 Redis 客户端实现,无需我们自己再手动实现):
-
找到 key 对应的所有 hash slot; -
分别向对应的 Redis 节点发起 mget
请求获取数据; -
等待所有请求执行结束,重新组装结果数据,保持跟入参 key 的顺序一致,然后返回结果。
如果想要解决这个多次网络传输的问题,比较常用的办法是自己维护 key 与 slot 的关系。不过这样不太灵活,虽然带来了性能提升,但同样让系统复杂性提升。
Redis Cluster 并没有使用一致性哈希,采用的是 哈希槽分区 ,每一个键值对都属于一个 hash slot(哈希槽) 。当客户端发送命令请求的时候,需要先根据 key 通过上面的计算公示找到的对应的哈希槽,然后再查询哈希槽和节点的映射关系,即可找到目标 Redis 节点。
pipeline
对于不支持批量操作的命令,我们可以利用 pipeline(流水线) 将一批 Redis 命令封装成一组,这些 Redis 命令会被一次性提交到 Redis 服务器,只需要一次网络传输。不过,需要注意控制一次批量操作的 元素个数(例如 500 以内,实际也和元素字节数有关),避免网络传输的数据量过大。
与mget
、mset
等原生批量操作命令一样,pipeline 同样在 Redis Cluster 上使用会存在一些小问题。原因类似,无法保证所有的 key 都在同一个 hash slot(哈希槽)上。如果想要使用的话,客户端需要自己维护 key 与 slot 的关系。
原生批量操作命令和 pipeline 的是有区别的,使用的时候需要注意:
-
原生批量操作命令是原子操作,pipeline 是非原子操作; -
pipeline 可以打包不同的命令,原生批量操作命令不可以; -
原生批量操作命令是 Redis 服务端支持实现的,而 pipeline 需要服务端和客户端的共同实现。
另外,pipeline 不适用于执行顺序有依赖关系的一批命令。就比如说,你需要将前一个命令的结果给后续的命令使用,pipeline 就没办法满足你的需求了。对于这种需求,我们可以使用 Lua 脚本 。
Lua 脚本
Lua 脚本同样支持批量操作多条命令。一段 Lua 脚本可以视作一条命令执行,可以看作是原子操作。一段 Lua 脚本执行过程中不会有其他脚本或 Redis 命令同时执行,保证了操作不会被其他指令插入或打扰,这是 pipeline 所不具备的。
分布式锁的 Lua 脚本:
并且,Lua 脚本中支持一些简单的逻辑处理比如使用命令读取值并在 Lua 脚本中进行处理,这同样是 pipeline 所不具备的。
不过, Redis Cluster 下 Lua 脚本的原子操作也无法保证了,原因同样是无法保证所有的 key 都在同一个 hash slot(哈希槽)上。
本文节选自 JavaGuide(javaguide.cn)。
👉如果本文对你有帮助的话,欢迎点赞、分享 ,这对我继续分享&创作优质文章非常重要。非常感谢!