百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

执行个 DEL 竟然也会阻塞 Redis?深挖一下果然不简单

mhr18 2024-11-12 11:14 13 浏览 0 评论

#头条创作挑战赛#

hello,大家好,我是张张,「架构精进之路」公号作者。


前两天有同事在执行 DEL 进行Redis数据删除的时候,阻塞Redis造成服务报警。

相信很多人对此不能理解:

  • DEL 操作不就是从Redis DB 字典中删除过期 key 么?
  • 它的时间复杂度不就是 O(1) 么?
  • 为什么会对Redis造成阻塞呢?

这篇文章,我就来和你盘点一下,使用 Redis DEL 会踩到「坑」,以及深挖下其中的原理。

1. DEL 命令的时间复杂度

删除一个 key,你肯定会用 DEL 命令,它的时间复杂度是多少呢?

是 O(1) 么?

其实不一定。

如果你有认真阅读 Redis 的官方文档,就会发现:删除一个 key 的耗时,与这个 key 的类型有关。

Redis 官方文档在介绍 DEL 命令时,是这样描述的:

  • key 是 String 类型,DEL 时间复杂度是 O(1)
  • key 是 List/Hash/Set/ZSet 类型,DEL 时间复杂度是 O(M),M 为元素数量

也就是说,如果你要删除的是一个非 String 类型的 key,这个 key 的元素越多,那么在执行 DEL 时耗时就越久!

为什么会是这样呢?

原因就在于:删除这种 key 时,Redis 需要依次释放每个元素的内存,元素越多,这个过程就会越耗时。

而这么长的操作耗时,势必会阻塞整个 Redis 实例,影响 Redis 的性能。

接下来,我们再来分析下,删除一个 String 类型的 key 会不会有这种问题?

前面不是提到过,Redis 官方文档的描述,删除 String 类型的 key,时间复杂度是 O(1) 么?这不会导致 Redis 阻塞吧?

其实,这也 不 一 定

2. Redis 的删除策略

Redis 主要提供两种删除策略:

  • 请求时删除:执行命令操作时,先检查下 key 是否已经过期,若已过期则删除,反之,继续操作。
  • 定期清理:请求时删除的补充,按一定的请求频率清理已过期的 key。

其中,针对定期清理 redis 提供了两种触发机制:

  • 一种是低频次,频率由周期性 server.hz 决定。
  • 另一种是高频次,频率与主事件循环有关,由 beforeSleep 方法触发。

其实不管哪种方式触发,都需要考虑这个问题:不能对服务造成长时间的阻塞

现实中总会有各种各样难以解决的问题:

  • 假如我们一次性过期很多 key,要一次性清理该怎么办?
  • 假如有个大 key,要花很长时间才能删除掉,又该怎么办?

你考虑一下,如果这个 key 占用的内存非常大呢?

例如,这个 key 存储了 800MB 的数据(很明显,它是一个 bigkey),那在执行 DEL 时,耗时依旧会变长!

这是因为,Redis 释放这么大的内存给操作系统,也是需要时间的,所以操作耗时也会变长。

所以,对于 String 类型来说,你最好也不要存储过大的数据,否则在删除它时,也会有性能问题。

3. 如何稳妥的进行 Redis 删除

当我们在删除 List/Hash/Set/ZSet 类型的 key 时,一定要格外注意,不能无脑执行 DEL,而是应该用以下方式删除:

  1. 查询元素数量:执行 LLEN/HLEN/SCARD/ZCARD 命令
  2. 判断元素数量:如果元素数量较少,可直接执行 DEL 删除,否则分批删除
  3. 分批删除:执行 LRANGE/HSCAN/SSCAN/ZSCAN + LPOP/RPOP/HDEL/SREM/ZREM 删除

主要采用以下两种原则来应对:

  • 少量、高频次 进行过期数据清理
  • 大 key 采用异步惰性删除策略,避免主线程阻塞

从触发删除时机来看,redis 主要有两种触发场景:

  • 请求时触发:对所有请求命令执行之前都检查下 key 是否过期。
  • 定期触发:定期的检查下是否有 key 过期,有的话就尝试删除。

关于请求时触发,本质上这是一种 惰性 的思想,你主动发起请求了,我就顺便帮你检查下当前状态,如果没有请求,那么我就不做任何处理。

站在系统的角度,它需要客户端触发,然后系统被动的接受指令。

此时,你可能会想:Redis 4.0 不是推出了 lazy-free 机制么?

打开这个机制,释放内存的操作会放到后台线程中执行,那是不是就不会阻塞主线程了?

那真的会是这样吗?

4. 什么是 lazy-free?

在应用之前,我们首先要搞清楚,什么是lazy-free?

lazy-free是4.0新增的功能,但是默认是关闭的,需要手动开启。这里的 lazy-free,将慢操作异步化,这也是在事件处理上向多线程迈进了一步。

惰性删除的意思是:将释放内存的操作交给后台线程异步的进行处理,也就意味着一个 key 真正意义上的删除,具有一定的延迟。

惰性,就体现在删除操作并没有真正执行,而是交给后台线程异步处理,可能比实际响应结果要晚一些才真正完成。

从 DB 中删除过期 key 时,只是从 DB 字典中将关系删除,内存没有真正释放,而是交给后台异步线程去删除。

主线程将待删除的 key 扔到 lazy_free 队列,并唤醒对应的后台线程,此时 bio_lazy_free 后台线程就从队列中取出对应的 key 进行内存清理。这也是一个典型的 生产者 - 消费者 模型。

5. DEL bigkey 会阻塞

手动开启lazy-free时,有4个选项可以控制:

  • lazyfree-lazy-expire:key在过期删除时尝试异步释放内存
  • lazyfree-lazy-eviction:内存达到maxmemory并设置了淘汰策略时尝试异步释放内存
  • lazyfree-lazy-server-del:执行RENAME/MOVE等命令或需要覆盖一个key时,删除旧key尝试异步释放内存
  • replica-lazy-flush:主从全量同步,从库清空数据库时异步释放内存

除了replica-lazy-flush之外,其他情况都只是可能去异步释放key的内存,并不是每次必定异步释放内存的。

开启lazy-free后,Redis在释放一个key的内存时,首先会评估代价,如果释放内存的代价很小,那么就直接在主线程中操作了,没必要放到异步线程中执行(不同线程传递数据也会有性能消耗)。

那什么情况才会真正异步释放内存?

这和key的类型、编码方式、元素数量都有关系:

  • 当Hash/Set底层采用哈希表存储(非ziplist/int编码存储)时,并且元素数量超过64个
  • 当ZSet底层采用跳表存储(非ziplist编码存储)时,并且元素数量超过64个
  • 当List链表节点数量超过64个(注意,不是元素数量,而是链表节点的数量,List的实现是在每个节点包含了若干个元素的数据,这些元素采用ziplist存储)
  • 再加一个条件就是refcount=1 就是没有人在引用这个key的时候

只有以上这些情况,在删除key释放内存时,才会真正放到异步线程中执行,其他情况一律还是在主线程操作。

也就是说String(不管内存占用多大)、List(少量元素)、Set(int编码存储)、Hash/ZSet(ziplist编码存储)这些情况下的key在释放内存时,依旧在主线程中操作。

由此可见,即使开启了lazy-free,String类型的bigkey,在删除一个 String 类型的 bigkey 时,它仍旧是在主线程中处理,而不是放到后台线程中执行,依旧有阻塞 Redis 的风险!

6. 总结

其实,接触任何一个新领域,都会经历陌生、熟悉、踩坑、吸收经验、游刃有余这几个阶段。

踩坑不可怕,根据问题再深挖一下,一定能收获到你期望的答案。

本文通过对Redis DEL 命令的一系列分析,总结来看,Redis 有自己的一套代价评估方法,认为可以清理代价较大,才会交给后台线程惰性处理;反之,顺手就给清理了。

最后忠告各位开发同学,即便Redis提供了lazy-free,我建议还是尽量不要在Redis中存储bigkey


我是 张张,一个对于技术有思考的资深后端程序员,在每一篇文章中,我不仅会告诉你一个技术点是什么,还会尝试把这些问题探究的思考过程提炼成通用的方法论,让你可以应用在其它领域中,做到举一反三。

如果文章对你有所帮助,还请帮忙点赞、在看、转发一下,你的支持会激励我输出更高质量的文章,非常感谢!

·END·



希望今天的讲解对大家有所帮助,谢谢!

Thanks for reading!

作者:架构精进之路,十年研发风雨路,大厂架构师,CSDN 博客专家,专注架构技术沉淀学习及分享,职业与认知升级,坚持分享接地气儿的干货文章,期待与你一起成长。
关注并私信我回复“01”,送你一份程序员成长进阶大礼包,欢迎勾搭。

相关推荐

Spring Boot3 连接 Redis 竟有这么多实用方式

各位互联网大厂的后端开发精英们,在日常开发中,想必大家都面临过系统性能优化的挑战。当系统数据量逐渐增大、并发请求不断增多时,如何提升系统的响应速度和稳定性,成为了我们必须攻克的难题。而Redis,这...

隧道 ssh -L 命令总结 和 windows端口转发配置

摘要:隧道ssh-L命令总结和windows端口转发配置关键词:隧道、ssh-L、端口转发、网络映射整体说明最近在项目中,因为内网的安全密级比较高,只能有一台机器连接内网数据库,推送...

火爆BOOS直聘的13个大厂Java社招面经(5年经验)助你狂拿offer

火爆BOOS直聘的13个大厂Java社招面经(5年经验)助你狂拿offer综上所述,面试遇到的所有问题,整理成了一份文档,希望大家能够喜欢!!Java面试题分享(Java中高级核心知识全面解析)一、J...

「第五期」游服务器一二三面 秋招 米哈游

一面下午2点,35分钟golang内存模型golang并发模型golanggc原理过程channel用途,原理redis数据结构,底层实现跳跃表查询插入复杂度进程,线程,协程kill原理除了kil...

RMQ——支持合并和优先级的消息队列

业务背景在一个项目中需要实现一个功能,商品价格发生变化时将商品价格打印在商品主图上面,那么需要在价格发生变动的时候触发合成一张带价格的图片,每一次触发合图时计算价格都是获取当前最新的价格。上游价格变化...

Redis 中的 zset 为什么要用跳跃表,而不是B+ Tree 呢?

Redis中的有序集合使用的是一种叫做跳跃表(SkipList)的数据结构来实现,而不是使用B+Tree。本文将介绍为什么Redis中使用跳跃表来实现有序集合,而不是B+Tree,并且探讨跳跃表...

一文让你彻底搞懂 WebSocket 的原理

作者:木木匠转发链接:https://juejin.im/post/5c693a4f51882561fb1db0ff一、概述上一篇文章《图文深入http三次握手核心问题【思维导图】》我们分析了简单的一...

Redis与Java整合的最佳实践

Redis与Java整合的最佳实践在这个数字化时代,数据处理速度决定了企业的竞争力。Redis作为一款高性能的内存数据库,以其卓越的速度和丰富的数据结构,成为Java开发者的重要伙伴。本文将带你深入了...

Docker与Redis:轻松部署和管理你的Redis实例

在高速发展的云计算时代,应用程序的部署和管理变得越来越复杂。面对各种操作系统、依赖库和环境差异,开发者常常陷入“在我机器上能跑”的泥潭。然而,容器化技术的兴起,尤其是Docker的普及,彻底改变了这一...

Java开发中的缓存策略:让程序飞得更快

Java开发中的缓存策略:让程序飞得更快缓存是什么?首先,让我们来聊聊什么是缓存。简单来说,缓存是一种存储机制,它将数据保存在更快速的存储介质中,以便后续使用时能够更快地访问。比如,当你打开一个网页时...

国庆临近,字节后端开发3+4面,终于拿到秋招第一个offer

字节跳动,先面了data部门,3面技术面之后hr说需要实习转正,拒绝,之后另一个部门捞起,四面技术面,已oc分享面经,希望对大家有所帮助,秋招顺利在文末分享了我为金九银十准备的备战资源库,包含了源码笔...

“快”就一个字!Redis凭什么能让你的APP快到飞起?

咱们今天就来聊一个字——“快”!在这个信息爆炸、耐心越来越稀缺的时代,谁不希望自己手机里的APP点一下“嗖”就打开,刷一下“唰”就更新?谁要是敢让咱用户盯着个小圈圈干等,那简直就是在“劝退”!而说到让...

双十一秒杀,为何总能抢到?Redis功不可没!

一年一度的双十一“剁手节”,那场面,简直比春运抢票还刺激!零点的钟声一敲响,亿万个手指头在屏幕上疯狂戳戳戳,眼睛瞪得像铜铃,就为了抢到那个心心念念的半价商品、限量版宝贝。你有没有发现一个奇怪的现象?明...

后端开发必看!为什么说Redis是天然的幂等性?

你在做后端开发的时候,有没有遇到过这样的困扰:高并发场景下,同一个操作重复执行多次,导致数据混乱、业务逻辑出错?别担心,很多同行都踩过这个坑。某电商平台就曾因订单创建接口在高并发时不具备幂等性,用户多...

开发一个app需要哪些技术和工具

APP开发需要一系列技术和工具的支持,以下是对这些技术的清晰归纳和分点表示:一、前端开发技术HTML用于构建页面结构。CSS用于样式设计和布局。JavaScript用于页面交互和逻辑处理。React...

取消回复欢迎 发表评论: