Redis随笔-rename效率问题

Redis随笔-rename效率问题,第1张

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

RENAME – Redis

        11 锁需要具备唯一性

        12 锁需要有超时时间,防止死锁

        13 锁的创建和设置锁超时时间需要具备原子性

        14 锁的超时的续期问题

        15 B的锁被A给释放了的问题

        16 锁的可重入问题

        17 集群下分布式锁的问题

        问题讲解:

        首先分布式锁要解决的问题就是分布式环境下同一资源被多个进程进行访问和 *** 作的问题,既然是同一资源,那么肯定要考虑数据安全问题其实和单进程下加锁解锁的原理是一样的,单进程下需要考虑多线程对同一变量进行访问和修改问题,为了保证同一变量不被多个线程同时访问,按照顺序对变量进行修改,就要在访问变量时进行加锁,这个加锁可以是重量级锁,也可以是基于cas的乐观锁

        解决方案:

        使用redis命令setnx(set if not exist),即只能被一个客户端占坑,如果redis实例存在唯一键(key),如果再想在该键(key)上设置值,就会被拒绝

        问题讲解:

        redis释放锁需要客户端的 *** 作,如果此时客户端突然挂了,就没有释放锁的 *** 作了,也意味着其他客户端想要重新加锁,却加不了的问题

        解决方案:

        所以,为了避免客户端挂掉或者说是客户端不能正常释放锁的问题,需要在加锁的同时,给锁加上超时时间

        即,加锁和给锁加上超时时间的 *** 作如下 *** 作:

>setnx lockkey true    #加锁 *** 作

ok

>expire lockkey 5    #给锁加上超时时间

do something critical

>del lockkey    #释放锁

(integer) 1

        问题讲解:

        通过23加锁和超时时间的设置可以看到,setnx和expire需要两个命令来完成 *** 作,也就是需要两次RTT *** 作,如果在setnx和expire两次命令之间,客户端突然挂掉,这时又无法释放锁,且又回到了死锁的问题

        解决方案:

        使用set扩展命令

        如下:

>set lockkey true ex 5 nx   #加锁,过期时间5s

ok

do something critical

>del lockkey

        以上的set lockkey true ex 5 nx命令可以一次性完成setnx和expire两个 *** 作,也就是解决了原子性问题

        问题讲解:

        redis分布式锁过期,而业务逻辑没执行完的场景,不过,这里换一种思路想问题,把redis锁的过期时间再弄长点不就解决了吗?那还是有问题,我们可以在加锁的时候,手动调长redis锁的过期时间,可这个时间多长合适?业务逻辑的执行时间是不可控的,调的过长又会影响 *** 作性能。

        解决方案:

        使用redis客户端redisson,redisson很好的解决了redis在分布式环境下的一些棘手问题,它的宗旨就是让使用者减少对Redis的关注,将更多精力用在处理业务逻辑上。redisson对分布式锁做了很好封装,只需调用API即可。RLock lock = redissonClientgetLock("stockLock");

        redisson在加锁成功后,会注册一个定时任务监听这个锁,每隔10秒就去查看这个锁,如果还持有锁,就对过期时间进行续期。默认过期时间30秒。这个机制也被叫做:“看门狗”

        问题讲解:

        A、B两个线程来尝试给key myLock加锁,A线程先拿到锁(假如锁3秒后过期),B线程就在等待尝试获取锁,到这一点毛病没有。那如果此时业务逻辑比较耗时,执行时间已经超过redis锁过期时间,这时A线程的锁自动释放(删除key),B线程检测到myLock这个key不存在,执行 SETNX命令也拿到了锁。但是,此时A线程执行完业务逻辑之后,还是会去释放锁(删除key),这就导致B线程的锁被A线程给释放了。

      解决方案:

      一般我们在每个线程加锁时要带上自己独有的value值来标识,只释放指定value的key,否则就会出现释放锁混乱的场景一般我们可以设置value为业务前缀_当前线程ID或者uuid,只有当前value相同的才可以释放锁

        问题讲解:

        上面我们讲了,为了保证锁具有唯一性,需要使用setnx,后来为了与超时时间一起设置,我们选用了set命令。 在我们想要在加锁期间,拥有锁的客户端想要再次获得锁,也就是锁重入

        解决方案:

       给锁设置hash结构的加锁次数,每次加锁就+1

        问题讲解:

        这一问题是在redis集群方案时会出现的事实上,现在为了保证redis的高可用和访问性能,都会设置redis的主节点和从节点,主节点负责写 *** 作,从节点负责读 *** 作,也就意味着,我们所有的锁都要写在主redis服务器实例中,如果主redis服务器宕机,资源释放(在没有加持久化时候,如果加了持久化,这一问题会更加复杂),此时redis主节点的数据并没有复制到从服务器,此时,其他客户端就会趁机获取锁,而之前拥有锁的客户端可能还在对资源进行 *** 作,此时又会出现多客户端对同一资源进行访问和 *** 作的问题

        解决方案:

        使用redlock,原理与zookeeper分布式锁原理相同多台主机超过半数设置成功则获取锁成功,要注意下主机个数必须是奇数,不过这有效率问题

下载安装包并解压;
redis window 服务器安装教程
2
解压安装包到自定义的文件夹后,打开dos窗口,切换至解压后目录。输入指令redis-server rediswindosconf 即可启动redis服务。
redis window 服务器安装教程
3
为了测试服务器是否启动正常,双击解压目录下的redis客户端,存放一个key值进行测试,如下图存取即表示您的redis启动成功啦。
redis window 服务器安装教程
redis window 服务器安装教程
END
让redis支持远程访问
以上步骤,您已经完成了如何在服务器本地使用redis。想要在本机访问服务器的redis服务,还需要修改rediswindowsconf文件的相关配置。将“bind 127001”注释,增加“bind 您的服务器IP”;为了安全,开启密码认证:“requirepass 密码”。
redis window 服务器安装教程
redis window 服务器安装教程
2
打开本机dos,调用redis-cli客户端进行测试。输入指令:redis-cli -h 服务器IP -p 6379 -a 密码
redis window 服务器安装教程

不会。Redis服务器挂掉,客户端尝试连接Redis数据库时就会失败,是因为Redis是一个内存型数据库,所有数据都保存在内存中,Redis服务器挂掉,则客户端无法访问这些数据,此时客户端尝试连接Redis数据库,就会收到连接超时或连接拒绝的错误消息。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zz/13464065.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-08-12
下一篇 2023-08-12

发表评论

登录后才能评论

评论列表(0条)

保存