热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

redis线程阻塞原因排插_Redis的自白:我为什么在单线程的这条路上越走越远?...

我是Redis,今年11岁了~曾几何时我是辣么的单纯,辣么的可爱,而如今我竟背叛了当初“誓言”,决心在多线程这条路上义无反顾
a574a395ca17ffc164c2ff9d9bb4f9e2.png

我是 Redis,今年 11 岁了~

曾几何时我是辣么的单纯,辣么的可爱,而如今我竟背叛了当初“誓言”,决心在多线程这条路上义无反顾的一路狂奔,没错我就是你们口中那个既可爱又迷人的 Redis,你可以叫我小 R...R 。

一波骚操作结束,我们开始今天的正文。

我们知道在 Redis 4.0 之后就陆陆续续添加了一些多线程的功能,难道单线程不香了吗?

单线程慢吗?

Redis 的单线程曾几何时还是我们炫耀的资本,优雅又不失高效的设计,让无数的追求者为之着迷。

你要问我排第几?Nginx 是我大哥,NodeJS 是我小弟,我在家中排名老二

我们兄弟仨可谓单线程的杰出代表,不仅演示了我们的优雅更加展现了我们的高效。

08c5f58892c7aa51fbe617af6165a46d.png

有人可能会问:为什么单线程的我,竟然如此嚣张?

家中有矿呗,Redis 单线程但性能依旧很快的主要原因有以下几点:

  1. 基于内存操作:Redis 的所有数据都存在内存中,因此所有的运算都是内存级别的,所以的性能比较高;
  2. 数据结构简单:Redis 的数据结构比较简单,是为 Redis 专门设计的,而这些简单的数据结构的查找和操作的时间复杂度都是 O(1),因此性能比较高;
  3. 多路复用和非阻塞 I/O:Redis 使用 I/O 多路复用功能来监听多个 socket 连接客户端,这样就可以使用一个线程连接来处理多个请求,减少线程切换带来的开销,同时也避免了 I/O 阻塞操作,从而大大提高了 Redis 的性能;
  4. 避免上下文切换:因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的消耗,而且单线程不会导致死锁问题的发生。

来看一下我的父亲大大是如何评价我的,Redis 的 FAQ(Frequently Asked Questions,常见问题)回答了单线程的这个问题,具体内容如下:

Redis is single threaded. How can I exploit multiple CPU / cores?
It's not very frequent that CPU becomes your bottleneck with Redis, as usually Redis is either memory or network bound. For instance, using pipelining Redis running on an average Linux system can deliver even 1 million requests per second, so if your application mainly uses O(N) or O(log(N)) commands, it is hardly going to use too much CPU.
However, to maximize CPU usage you can start multiple instances of Redis in the same box and treat them as different servers. At some point a single box may not be enough anyway, so if you want to use multiple CPUs you can start thinking of some way to shard earlier.
You can find more information about using multiple Redis instances in the Partitioning page.
However with Redis 4.0 we started to make Redis more threaded. For now this is limited to deleting objects in the background, and to blocking commands implemented via Redis modules. For future releases, the plan is to make Redis more and more threaded.

他的大体意思是说 Redis 是基于内存操作的,因此的瓶颈可能是机器的内存或者网络带宽而并非 CPU,既然 CPU 不是瓶颈,那么自然就采用单线程的解决方案了,况且使用多线程比较麻烦。但是在 Redis 4.0 中开始支持多线程了,例如后台删除等功能。

简单来说,Redis 4.0 之前一直采用单线程的主要原因有以下三个:

  1. 使用单线程模型是 Redis 的开发和维护更简单,因为单线程模型方便开发和调试;
  2. 即使使用单线程模型也并发的处理多客户端的请求,主要使用的是多路复用和非阻塞 IO;
  3. 对于 Redis 系统来说,主要的性能瓶颈是内存或者网络带宽而并非 CPU。
15661f3fedb7f6f17303e820afcbc6b5.png

为什么需要多线程?

但是单线程也有单线程的苦恼,比如当我(Redis)需要删除一个很大的数据时,因为是单线程同步操作,这就会导致 Redis 服务卡顿,于是在 Redis 4.0 中就新增了多线程的模块,当然此版本中的多线程主要是为了解决删除数据效率比较低的问题的,的相关指令有以下三个:

  1. unlink key
  2. flushdb async
  3. flushall async

执行示例如下所示:

> unlink key # 后台删除某个 key

OK # 执行成功
flushall async # 清空所有数据
OK # 执行成功

这样我就可以把这些坏人“瞬间”拉黑(删除)了。

4048dcff988b4a5226981bfed29955f2.png

所谓的“瞬间”删除其实有些夸张,只是从返回的结果来看是删除成功了,但是这只是把删除工作交给了后台的小弟(子线程)异步来删除数据了

小贴士:正常情况下使用 del 指令可以很快的删除数据,而当被删除的 key 是一个非常大的对象时,例如时包含了成千上万个元素的 hash 集合时,那么 del 指令就会造成 Redis 主线程卡顿,因此使用惰性删除可以有效的避免 Redis 卡顿的问题。

Redis 6 中的多线程

之前在 Redis 4.0 中你说删除比较慢,骗我开大(多线程)来处理也就罢了,为毛 Redis 6.0 还要多线程嘞?

其实是这样的在 Redis 4.0 版本中虽然引入了多线程,但此版本中的多线程只能用于大数据量的异步删除,然而对于非删除操作的意义并不是很大。

但如果我们使用我们在非删除的环境下使用多线程的话就可以分摊 Redis 同步读写 I/O 的压力,以及充分的利用多核 CPU 的资源了,这样就可以有效的提升 Redis 的 QPS(Query Per Second,每秒查询率)了。

在 Redis 中虽然使用了 I/O 多路复用,并且是基于非阻塞 I/O 进行操作的,但 I/O 的读和写本身是堵塞的,比如当 socket 中有数据时,Redis 会通过调用先将数据从内核态空间拷贝到用户态空间,再交给 Redis 调用,而这个拷贝的过程就是阻塞的,当数据量越大时拷贝所需要的时间就越多,而这些操作都是基于单线程完成的。

ac2349a9401b43fac74740c68d32ee29.png
I/O 多路复用,简单来说就是通过监测文件的读写事件,再通知线程执行相关操作,保证 Redis 的非阻塞 I/O 能够顺利执行完成的机制。

因此在 Redis 6.0 中新增了多线程的功能来提高 I/O 的读写性能,他的主要实现思路是将主线程的 IO 读写任务拆分给一组独立的线程去执行,这样就可以使多个 socket 的读写可以并行化了,但 Redis 的命令依旧是由主线程串行执行的

需要注意的是 Redis 6.0 默认是禁用多线程的,可以通过修改 Redis 的配置文件 redis.conf 中的 io-threads-do-reads 等于 true 来开启多线程,完整配置为 io-threads-do-reads true,除此之外我们还需要设置线程的数量才能正确的开启多线程的功能,同样是修改 Redis 的配置,例如设置 io-threads 4 表示开启 4 个线程。

小贴士:关于线程数的设置,官方的建议是如果为 4 核的 CPU,建议线程数设置为 2 或 3,如果为 8 核 CPU 建议线程数设置为 6,线程数一定要小于机器核数,线程数并不是越大越好。

关于 Redis 的性能,我的父王 antirez(Redis 作者)在 RedisConf 2019 分享时曾提到,Redis 6 引入的多线程 I/O 特性对性能提升至少是一倍以上。国内也有人在阿里云使用 4 个线程的 Redis 版本和单线程的 Redis 进行比较测试,发现测试的结果和 antirez 给出的结论基本吻合,性能基本可以提高一倍

e14484e8a42982966809c0efa71bd8f0.png

总结

Redis 虽然依靠自己的:基于内存操作、数据结构简单、多路复用和非阻塞 I/O、避免了不必要的线程上下文切换等特性,在单线程的环境下依然很快;但对于大数据的 key 删除还是卡的飞起,因此在 Redis 4.0 引入了多线程:unlink key/flushall async 等命令,主要用于 Redis 数据的删除,而在 Redis 6.0 中引入了 I/O 多线程的读写,这样就可以更加高效的处理更多的任务了,Redis 只是将 I/O 读写变成了多线程,而命令的执行依旧是由主线程串行执行的,因此在多线程下操作 Redis 不会出现线程安全的问题。

Redis 无论是当初的单线程设计,还是如今与当初设计相背的多线程,目的只有一个:让 Redis 变得越来越快。

所以 Redis 依旧没变,他还是那个曾经的追风少年~

原文:Java中文社群

https://mp.weixin.qq.com/s/Lf87PnraSm5UgxUhJ7fq5A



推荐阅读
  • 本文详细解析了 MySQL 5.7.20 版本中二进制日志(binlog)崩溃恢复机制的工作流程。假设使用 InnoDB 存储引擎,并且启用了 `sync_binlog=1` 配置,文章深入探讨了在系统崩溃后如何通过 binlog 进行数据恢复,确保数据的一致性和完整性。 ... [详细]
  • 在《孙鑫VC++讲座笔记第三讲》中,深入探讨了MFC应用程序的架构,并详细解析了SDI程序的流程图。AfxWinMain()函数位于WINMAIN.CPP文件中,其主要职责包括调用AfxWinInit()进行框架初始化,以及通过pApp->InitApplication()执行应用内部的初始化过程。此外,还详细分析了各个关键函数的作用及其在程序启动过程中的具体功能。 ... [详细]
  • Go语言实现Redis客户端与服务器的交互机制深入解析
    在前文对Godis v1.0版本的基础功能进行了详细介绍后,本文将重点探讨如何实现客户端与服务器之间的交互机制。通过具体代码实现,使客户端与服务器能够顺利通信,赋予项目实际运行的能力。本文将详细解析Go语言在实现这一过程中的关键技术和实现细节,帮助读者深入了解Redis客户端与服务器的交互原理。 ... [详细]
  • JVM参数设置与命令行工具详解
    JVM参数配置与命令行工具的深入解析旨在优化系统性能,通过合理设置JVM参数,确保在高吞吐量的前提下,有效减少垃圾回收(GC)的频率,进而降低系统停顿时间,提升服务的稳定性和响应速度。此外,本文还将详细介绍常用的JVM命令行工具,帮助开发者更好地监控和调优JVM运行状态。 ... [详细]
  • 本文深入探讨了NDK与JNI技术在实际项目中的应用及其学习路径。通过分析工程目录结构和关键代码示例,详细介绍了如何在Android开发中高效利用NDK和JNI,实现高性能计算和跨平台功能。同时,文章还提供了从基础概念到高级实践的系统学习指南,帮助开发者快速掌握这些关键技术。 ... [详细]
  • 第五章详细探讨了 Red Hat Enterprise Linux 6 中的 Ext3 文件系统。5.1 节介绍了如何创建 Ext3 文件系统,包括必要的命令和步骤,以及在实际操作中可能遇到的问题和解决方案。此外,还涵盖了 Ext3 文件系统的性能优化和维护技巧,为用户提供全面的操作指南。 ... [详细]
  • 本文深入探讨了 Spring Cloud 微服务架构中 Gateway 组件的应用,详细介绍了其在实现高效请求路由与过滤方面的关键作用。文章首先从基本配置入手,逐步讲解了如何通过静态路由和动态路由实现灵活的服务访问控制。此外,还特别介绍了如何配置 Gateway 以自动从 Nacos 服务注册中心拉取服务列表,进一步提升系统的可维护性和扩展性。 ... [详细]
  • 本文探讨了将PEBuilder转换为DIBooter.sh的方法,重点介绍了如何将DI工具集成到启动层,实现离线镜像引导安装。通过使用DD命令替代传统的grub-install工具,实现了GRUB的离线安装。此外,还详细解析了bootice工具的工作原理及其在该过程中的应用,确保系统在无网络环境下也能顺利引导和安装。 ... [详细]
  • 本文将详细介绍在Android应用中添加自定义返回按钮的方法,帮助开发者更好地理解和实现这一功能。通过具体的代码示例和步骤说明,本文旨在为初学者提供清晰的指导,确保他们在开发过程中能够顺利集成返回按钮,提升用户体验。 ... [详细]
  • Android 图像色彩处理技术详解
    本文详细探讨了 Android 平台上的图像色彩处理技术,重点介绍了如何通过模仿美图秀秀的交互方式,利用 SeekBar 实现对图片颜色的精细调整。文章展示了具体的布局设计和代码实现,帮助开发者更好地理解和应用图像处理技术。 ... [详细]
  • 结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法
    结语 | 《探索二进制世界:软件安全与逆向分析》读书笔记:深入理解二进制代码的逆向工程方法 ... [详细]
  • 深入解析:使用C++实现Python字节数组(struct)的高效处理方法 ... [详细]
  • 进程(Process)是指计算机中程序对特定数据集的一次运行活动,是系统资源分配与调度的核心单元,构成了操作系统架构的基础。在早期以进程为中心的计算机体系结构中,进程被视为程序的执行实例,其状态和控制信息通过任务描述符(task_struct)进行管理和维护。本文将深入探讨进程的概念及其关键数据结构task_struct,解析其在操作系统中的作用和实现机制。 ... [详细]
  • 本文探讨了在Lumen框架中实现自定义表单验证功能的方法与挑战。Lumen的表单验证机制默认返回无状态的JSON格式API响应,这给初学者带来了一定的难度。通过深入研究Validate类,作者分享了如何有效配置和使用自定义验证规则,以提升表单数据的准确性和安全性。 ... [详细]
  • 表面缺陷检测数据集综述及GitHub开源项目推荐
    本文综述了表面缺陷检测领域的数据集,并推荐了多个GitHub上的开源项目。通过对现有文献和数据集的系统整理,为研究人员提供了全面的资源参考,有助于推动该领域的发展和技术进步。 ... [详细]
author-avatar
赵小坑_38825
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有