资讯专栏INFORMATION COLUMN

Redis随笔-rename效率问题

sixleaves / 698人阅读

摘要:现象先搭建一个服务器,版本号为,看看它的内存信息接着用给创建一个名为的大,有个,每个的值都是这时候我们看看的内存占用情况由于大的创建,内存占用多了多兆。但其实这个不起眼的命令也可能造成一样的问题,使用时需要谨慎对待。

背景

rename是redis中给key重命名命令,rename key newkey的意思就是将key重命名为newkey。
大部分文档在介绍rename的时候只将它描述成一个时间复杂度为O(1)的命令,却忘了说明它可能导致的性能问题(涉及覆盖旧值的时候 时间复杂度应该是O(1)+O(M))。

我们先做个试验看看rename的问题。

现象

先搭建一个redis服务器,版本号为3.2,看看它的内存信息

127.0.0.1:8401> info memory
# Memory
used_memory:842416
used_memory_human:822.67K

接着用lua给redis创建一个名为 test的大key,test有500w个field,每个field的值都是1

127.0.0.1:8401> eval "for i=1,5000000,1 do redis.call("hset","test", i,1) end" 0
(nil)
(11.61s)
127.0.0.1:8401> hlen test
(integer) 5000000

这时候我们看看redis的内存占用情况

127.0.0.1:8401> info memory
# Memory
used_memory:381185592
used_memory_human:363.53M

由于大key test的创建,redis内存占用多了300多兆。
接下来我们创建一个临时key,并用它来rename掉大key test

127.0.0.1:8401> set tmp 1
OK
127.0.0.1:8401> rename tmp test
OK
(2.36s)

这时就能看到执行时间的异常了,rename执行时间长达2.36秒,这是为什么呢?我们再看看redis内存占用情况:

127.0.0.1:8401> info memory
# Memory
used_memory:821528
used_memory_human:802.27K

通过info返回的信息我们可以发现在执行rename之后redis将大key test大小为300多兆的值对象直接删除并回收掉了,而redis删除一个key的时间复杂度是O(M),在这里M是被删除的成员数量---500w。应该就是这个"隐式"删除操作导致了高延迟的产生。

文档

我们看看官方文档是怎么描述rename这一行为的:

RENAME key newkey

Renames key to newkey. It returns an error when key does not exist. If newkey already exists it is overwritten, when this happens RENAMEexecutes an implicit DEL operation, so if the deleted key contains a very big value it may cause high latency even if RENAME itself is usually a constant-time operation.

newkey如果本就存在,redis会用key的值覆盖掉newkey的值,而newkey原本的值会被redis隐式地删除。我们知道大key的删除伴随着高延迟(redis是单进程服务,服务器会在删除大key期间block住接下来其他命令的执行),这就导致时间复杂度本为O(1)的rename也有可能卡住redis。

这句官方文档的原话我没在其他文档里找到类似的翻译,看这些文档的开发者可能会误以为这是个特别安全的O(1)命令。

既然文档里已经说明了这种行为的存在,我就顺便看看源码这块逻辑是怎么走的:

源码分析
db.c
void renameCommand(client *c) {                                                                                        
    renameGenericCommand(c,0);
}
void renameGenericCommand(client *c, int nx) {
    robj *o;
    ...
    if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL) //旧key的值对象地址复制给o
        return;
    ...
    incrRefCount(o); //旧key的值对象引用计数+1(被o引用)
    if (lookupKeyWrite(c->db,c->argv[2]) != NULL) { //如果新key已经有值对象了
        ...
        dbDelete(c->db,c->argv[2]); //新key从db中移除、并将新key的值对象引用计数-1(变为0),并释放内存
    }
    dbAdd(c->db,c->argv[2],o);  //将新key => 旧key的值对象的组合放入db中
    ...
    dbDelete(c->db,c->argv[1]); //旧key从db中移除、并将旧key的值对象引用计数-1(不会变为0),不释放内存
    ...
}

正常O(1)重命名的逻辑不用多说,涉及到覆盖的过程可以简化成如下图:

在改变指针的指向之前,redis会先用if (lookupKeyWrite(c->db,c->argv[2]) != NULL)判断newkey是否有对应的值,若有 则调用dbDelete(c->db,c->argv[2]);将newkey的值v2删掉。

结论

用redis的时候,keyshgetalldel 这些命令我们会多加小心,因为不合理地调用它们可能会长时间block住redis的其他请求 甚至导致CPU使用率居高不下从而卡住整个服务器。但其实rename这个不起眼的命令也可能造成一样的问题,使用时需要谨慎对待。

参考资料

RENAME – Redis

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/36942.html

相关文章

  • Redis随笔-rename效率问题

    摘要:现象先搭建一个服务器,版本号为,看看它的内存信息接着用给创建一个名为的大,有个,每个的值都是这时候我们看看的内存占用情况由于大的创建,内存占用多了多兆。但其实这个不起眼的命令也可能造成一样的问题,使用时需要谨慎对待。 背景 rename是redis中给key重命名命令,rename key newkey的意思就是将key重命名为newkey。大部分文档在介绍rename的时候只将它描述...

    Hydrogen 评论0 收藏0
  • Redis入门系列(五):Redis的Set类型

    摘要:与的比较元素无序,元素不可重复。好用的特性可以进行差集交集并集的运算。注意有个特性有序元素可重复。可以,但是不优雅,。类型使用类型,就很容易的对某个标签进行增加删除更改。 概念 可以想象一下高中学过的集合,一样。 SET与LIST的比较 SET:元素无序,元素不可重复。LIST:元素有序,元素可重复。 SET好用的特性 SET可以进行差集、交集、并集的运算。 命令 添加、删除元素 SA...

    付永刚 评论0 收藏0
  • redis学习笔记(四):键管理

    摘要:本章将按照单个键遍历键数据库管理三个维度对一些通用命令进行介绍单键管理针对单个键的命令前面几节已经介绍过一部分了例如等下面介绍几个重要命令键重命名例如一个键名为值为下面操作将键改为如果在之前键已经存在那么它的值也将被覆盖为了防止被强行提供了 本章将按照单个键,遍历键,数据库管理三个维度对一些通用命令进行介绍. 1. 单键管理 针对单个键的命令,前面几节已经介绍过一部分了,例如type,...

    shuibo 评论0 收藏0
  • Redis的正确使用姿势

    摘要:但是用的人多了,就会出现很多不规范或者疏忽的地方,严重的时候甚至会导致生产事故,所以我们有必要来聊聊在使用过程中的一些正确姿势。使用的时候需要注意版本,当版本低于时,需升级才能使用。禁用高危命令中有很多高危命令。 前言 说到分布式缓存,可能大多数人脑海浮现的就是redis了,为什么redis能够在竞争激烈的缓存大战中脱颖而出呢?原因无非有一下几点:性能好,丰富的特性跟数据结构,api操...

    codeKK 评论0 收藏0
  • php 扩展安装(随笔

    摘要:查看扩展探针扩展相关函数管理扩展配置下安装流程下载对应的版本一定要选择正确下载扩展的版本下载地址选择对应的版本版本版本在中开启扩展,配置扩展相关的参数有需要的话重启服务器下安装流程直装流程把相应的扩展移动到你的文件夹下面然后在中开启相应的扩 1 查看php扩展(1)phpinfo 探针(2)php扩展相关函数get_loaded_extensions() arrayextension...

    y1chuan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<