Redis 属于单线程还是多线程?不同的版本有什么区别?

Redis 是普及率最高的技术之一,同时也是面试中必问的一个技术模块,所以从今天开始我们将从最热门的 Redis 面试题入手,更加深入的学习和了解一下 Redis。

我们本文的面试题是 Redis 属于单线程还是多线程?

典型回答

本文的问题在不同的 Redis 版本下答案是不同的,在 Redis 4.0 之前,Redis 是单线程运行的,但单线程并不意味着性能低,类似单线程的程序还有 Nginx 和 NodeJs 他们都是单线程程序,但是效率并不低。 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.

详情请见:https://redis.io/topics/faq

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

简单来说 Redis 之所以在 4.0 之前一直采用单线程的模式是因为以下三个原因:

  • 使用单线程模型是 Redis 的开发和维护更简单,因为单线程模型方便开发和调试;
  • 即使使用单线程模型也并发的处理多客户端的请求,主要使用的是多路复用(详见本文下半部分);
  • 对于 Redis 系统来说,主要的性能瓶颈是内存或者网络带宽而并非 CPU。

Redis 在 4.0 中引入了惰性删除(也可以叫异步删除),意思就是说我们可以使用异步的方式对 Redis 中的数据进行删除操作了,例如 unlink key / flushdb async / flushall async 等命令,他们的执行示例如下:

> unlink key # 后台删除某个 key
> OK # 执行成功
> flushall async # 清空所有数据
> OK # 执行成功

这样处理的好处是不会导致 Redis 主线程卡顿,会把这些删除操作交给后台线程来执行。

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

考点分析

关于 Redis 线程模型的问题(单线程或多线程)几乎是 Redis 必问的问题之一,但能回答好的人却寥寥无几,大部分的人只能回到上来 Redis 是单线程的以及说出来单线程的众多好处,但对于 Redis 4.0 和 Redis 6.0 中,尤其是 Redis 6.0 中的多线程能回答上来的人少之又少,和这个知识点相关的面试题还有以下这些。

  • Redis 主线程既然是单线程的,为什么还这么快?
  • 介绍一下 Redis 中的多路复用?
  • 介绍一下 Redis 6.0 中的多线程?

知识扩展

1.Redis 为什么这么快?

我们知道 Redis 4.0 之前是单线程的,那既然是单线程为什么还能这么快?

Redis 速度比较快的原因有以下几点:

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

官方使用基准测试的结果是,单线程的 Redis 吞吐量可以达到 10W/每秒,如下图所示: image.png

2.I/O 多路复用

套接字的读写方法默认情况下是阻塞的,例如当调用读取操作 read 方法时,缓冲区没有任何数据,那么这个线程就会阻塞卡在这里,直到缓冲区有数据或者是连接被关闭时,read 方法才可以返回,线程才可以继续处理其他业务。

但这样显然降低了程序的整体执行效率,而 Redis 使用的就是非阻塞的 I/O,这就意味着 I/O 的读写流程不再是阻塞的,读写方法都是瞬间完成并返回的,也就是他会采用能读多少读多少能写多少写多少的策略来执行 I/O 操作,这显然更符合我们对性能的追求。

但这种非阻塞的 I/O 依然存在一个问题,那就是当我们执行读取数据操作时,有可能只读取了一部分数据,同样写入数据也是这种情况,当缓存区满了之后我们的数据还没写完,剩余的数据何时写何时读就成了一个问题。

而 I/O 多路复用就是解决上面的这个问题的,使用 I/O 多路复用最简单的实现方式就是使用 select 函数,此函数为操作系统提供给用户程序的 API 接口,是用于监控多个文件描述符的可读和可写情况的,这样就可以监控到文件描述符的读写事件了,当监控到相应的事件之后就可以通知线程处理相应的业务了,这样就保证了 Redis 读写功能的正常执行了。

I/O 多路复用执行流程如下图所示: image.png

小贴士:现在的操作系统已经很少使用 select 函数了,改为调用 epoll(linux)和 kqueue(MacOS)等函数了,因为 select 函数在文件描述符特别多时性能非常的差。

3.Redis 6.0 多线程

Redis 单线程的优点很明显,不但降低了 Redis 内部的实现复杂性,也让所有操作都可以在无锁的情况下进行操作,并且不存在死锁和线程切换带来的性能和时间上的消耗,但缺点也很明显,单线程的机制导致 Redis 的 QPS(Query Per Second,每秒查询率)很难得到有效的提高。

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

如果我们使用多线程就可以分摊 Redis 同步读写 I/O 的压力,以及充分的利用多核 CPU 的资源,并且可以有效的提升 Redis 的 QPS。在 Redis 中虽然使用了 I/O 多路复用,并且是基于非阻塞 I/O 进行操作的,但 I/O 的读和写本身是堵塞的,比如当 socket 中有数据时,Redis 会通过调用先将数据从内核态空间拷贝到用户态空间,再交给 Redis 调用,而这个拷贝的过程就是阻塞的,当数据量越大时拷贝所需要的时间就越多,而这些操作都是基于单线程完成的。

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

总结

本文我们介绍了 Redis 在 4.0 之前单线程依然很快的原因:基于内存操作、数据结构简单、多路复用和非阻塞 I/O、避免了不必要的线程上下文切换,在 Redis 4.0 中已经添加了多线程的支持,主要体现在大数据的异步删除功能上,例如 unlink key、flushdb async、flushall async 等,Redis 6.0 新增了多线程 I/O 的读写并发能力,用于更好的提高 Redis 的性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/545342.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【原创】STM32下波特率计算详解

波特率的计算STM32下的波特率和串口外设时钟息息相关,USART 1的时钟来源于APB2,USART 2-5的时钟来源于APB1。在STM32中,有个波特率寄存器USART_BRR,如下: STM32串口波特率通过USART_BRR进行设置,STM32的波特…

java对数组进行排序_用Java对数组进行排序所需的最少交换

java对数组进行排序Problem: 问题: In this problem, we would have an unordered array with consecutive distinct natural numbers [1,2,3,..n], where n is the size of the array. We have to find the minimum number of swaps required to sort the array i…

如何实现查询附近的人?

查询附近的人或者是附近的商家是一个实用且常用的功能,比如微信中“附近的人”或是美团外卖中“附近商家”等,如下图所示: 那它是如何实现的呢?我们本文就一起来看。 我们本文的面试题是,使用 Redis 如何实现查询附近的人? 典型回答 在说如何实现地理位置查询之前,首…

第五六七章(PTA复习)

第五六七章图形界面线程IO图形界面 主要的布局管理器类包括流布局(FlowLayout)、边界布局(BorderLayout)、网格布局(GridLayout)、卡 片 布 局 (CardLayout) 、 网 格 包 布 局(CardBagLayout) 线程 答案:B IO

Redis 有哪些数据类型?

Redis 的数据类型可谓是 Redis 的精华所在,同样的数据类型,例如字符串存储不同的值对应的实际存储结构也是不同,当你存储的 int 值是实际的存储结构也是 int,如果是短字符串(小于 44 字节)实际存储的结构为…

导出/入数据库

导出/入数据库1、以SQL文件的方式1.1导出1.2 导入2、以mdf和ldf数据库文件的方式2.1导出2.1.1 脱机2.1.2 到数据库的数据路径,拷贝出mdf,ldf文件2.1.3 将原数据库设置为online状态即可正常使用2.2导入数据库(切记导入之前要先将控制权限打开)…

第八章Transact-SQL程序设计

第八章Transact-SQL程序设计8.1_变量8.1.1_局部变量8.1.2_全局变量8.2_流程控制语句8.2.1_IF...ELSE语句8.2.2_while循环语句8.1_变量 8.1.1_局部变量 局部变量的声明定义: Declare Variable_name Datatype[, Variable_name Datatype]…--举例: decla…

如何删除多余系统引导项

我们很多人都装过双系统,但是有时候装的当中却不想装了或者装不成功,生成的多余系统引导项怎么删除呢?下面分享下我的经验:win7(XP)下如何删除多余的系统引导项。关键词:删除多余系统引导项&…

动态规划编程面试_面试的前25大动态编程问题

动态规划编程面试Dynamic programming is one of the most asked paradigms in any product-based company interviews. You can expect DP in online assessments also if you are in touch with any product-based company. For beginner its, of course, a Tough nut to cra…

Redis 内存用完会怎样?

在某些极端情况下,软件为了能正常运行会做一些保护性的措施,比如运行内存超过最大值之后的处理,以及键值过期之后的处理等,都属于此类问题,而专业而全面的回答这些问题恰好是一个工程师所具备的优秀品质。 我们本文的面试题是 Redis 内存用完之后会怎么? 典型回答 Red…

Hapoxy+keepalived实现双主高可用负载均衡

在测试了NginxKeepalived的负载均衡后,也对HaproxyKeepalived双主模式做了测试,双主的模式充分利用了服务器资源,这样不会导致服务器浪费。 这里举例说明: 默认情况下,第一台负载均衡器主要分发 www.breaklinux的请求…

c语言中的printf函数_C语言中的printf()函数与示例

c语言中的printf函数C语言中的printf()函数 (printf() function in C) The printf() function is defined in the <stdio.h> header file. 在<stdio.h>头文件中定义了printf()函数 。 Prototype: 原型&#xff1a; int printf(const char* str, . . .);Parameter…

第一章数据库绪论

第一章数据库绪论1.1_数据库系统概述1.1.1_数据库的四个基本概念1.1.2_数据库系统的特点1.2_数据库模型1.2.1_两类数据模型1.2.2_概念模型1.2.3_数据模型的组成要素1.2.4_常用的数据模型1.3_数据库系统的结构1.3.2_数据库系统的三级模式结构1.3.3_数据库的二级映像功能与数据独…

如何保证 Redis 消息队列中的数据不丢失?

Redis 最常见的业务场景就是缓存读取与存储,而随着时间的推移,有人开始将它作为消息队列来使用了,并且随着 Redis 版本的发展,在 Redis.2.0.0 中新增了发布订阅模式(Pub/Sub)代表着官方开始正式支持消息队列的功能了,直到今天为止还有部分公司在实现轻量级的消息队列时,…

9款基于CSS3 Transitions实现的鼠标经过图标悬停特效

之前给大家分享了很多css3实现的按钮特效。今天给大家分享9款基于CSS3 Transitions实现的鼠标经过图标悬停特效。这款特效适用浏览器&#xff1a;360、FireFox、Chrome、Safari、Opera、傲游、搜狗、世界之窗. 不支持IE8及以下浏览器。效果图如下&#xff1a; 在线预览 源码下…

Redis 如何实现分布式锁?

锁是多线程编程中的一个重要概念,它是保证多线程并发时顺利执行的关键。我们通常所说的“锁”是指程序中的锁,也就是单机锁,例如 Java 中的 Lock 和 ReadWriteLock 等,而所谓的分布式锁是指可以使用在多机集群环境中的锁。 我们本文的面试题是,使用 Redis 如何实现分布式…

第二章关系数据库

第二章关系数据库2.1 关系模型概述&#xff08;略&#xff09;2.2 关系操作2.2.1_基本关系操作2.2.2_关系数据库语言的分类2.3 关系的完整性2.3.1_关系的三类完整性约束2.3.2_实体完整性2.3.3_参照完整性2.3.4_用户定义的完整性2.4 关系代数2.4.1_传统的集合运算2.4.2_专门的关…

线程----Monitor(互斥锁)类设置超时值

Monitor类与Lock语句相比&#xff0c;Monitor类的主要优点是&#xff1a;可以添加一个等待被锁定的超时值。缺点&#xff1a;开销非常大using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.T…

第三章关系数据库标准语言SQL

第三章关系数据库标准语言SQL3.1_SQL的特点3.3_数据定义3.3.1_基本表的定义、删除与修改3.3.3_索引的建立与删除3.4_数据查询3.4.1_查询时消除重复行3.4.2_涉及空值的查询3.4.3_BETWEEN AND的使用3.4.4_字符匹配3.4.5_聚集函数3.5_数据更新3.5.1_插入数据3.5.2_修改数据3.5.3_删…

使用 Redis 如何实现延迟队列?

延迟消息队列在我们的日常工作中经常会被用到&#xff0c;比如支付系统中超过 30 分钟未支付的订单&#xff0c;将会被取消&#xff0c;这样就可以保证此商品库存可以释放给其他人购买&#xff0c;还有外卖系统如果商家超过 5 分钟未接单的订单&#xff0c;将会被自动取消&…