为什么单线程的 Redis 能那么快?

news2024/11/18 16:44:45

大家好我是苏麟 , 给大家找一些好的文章看看 .

原文文章 : 03 高性能IO模型:为什么单线程Redis能那么快? (lianglianglee.com)

Redis 为什么用单线程?

要更好地理解 Redis 为什么用单线程,我们就要先了解多线程的开销。

多线程的开销

日常写程序时,我们经常会听到一种说法:“使用多线程,可以增加系统吞吐率,或是可以增加系统扩展性。”的确,对于一个多线程的系统来说,在有合理的资源分配的情况下,可以增加系统中处理请求操作的资源实体,进而提升系统能够同时处理的请求数,即吞吐率。下面的左图是我们采用多线程时所期待的结果。

但是,请你注意,通常情况下,在我们采用多线程后,如果没有良好的系统设计,实际得到的结果,其实是右图所展示的那样。我们刚开始增加线程数时,系统吞吐率会增加,但是,再进一步增加线程时,系统吞吐率就增长迟缓了,有时甚至还会出现下降的情况。

线程数与系统吞吐率

为什么会出现这种情况呢?一个关键的瓶颈在于,系统中通常会存在被多线程同时访问的共享资源,比如一个共享的数据结构。当有多个线程要修改这个共享资源时,为了保证共享资源的正确性,就需要有额外的机制进行保证,而这个额外的机制,就会带来额外的开销。

拿 Redis 来说,在上节课中,我提到过,Redis 有 List 的数据类型,并提供出队(LPOP)和入队(LPUSH)操作。假设 Redis 采用多线程设计,如下图所示,现在有两个线程 A 和 B,线程 A 对一个 List 做 LPUSH 操作,并对队列长度加 1。同时,线程 B 对该 List 执行 LPOP 操作,并对队列长度减 1。为了保证队列长度的正确性,Redis 需要让线程 A 和 B 的 LPUSH 和 LPOP 串行执行,这样一来,Redis 可以无误地记录它们对 List 长度的修改。否则,我们可能就会得到错误的长度结果。这就是多线程编程模式面临的共享资源的并发访问控制问题

多线程并发访问Redis

并发访问控制一直是多线程开发中的一个难点问题,如果没有精细的设计,比如说,只是简单地采用一个粗粒度互斥锁,就会出现不理想的结果:即使增加了线程,大部分线程也在等待获取访问共享资源的互斥锁,并行变串行,系统吞吐率并没有随着线程的增加而增加。

而且,采用多线程开发一般会引入同步原语来保护共享资源的并发访问,这也会降低系统代码的易调试性和可维护性。为了避免这些问题,Redis 直接采用了单线程模式。

讲到这里,你应该已经明白了“Redis 为什么用单线程”,那么,接下来,我们就来看看,为什么单线程 Redis 能获得高性能。

单线程 Redis 为什么那么快?

通常来说,单线程的处理能力要比多线程差很多,但是 Redis 却能使用单线程模型达到每秒数十万级别的处理能力,这是为什么呢?其实,这是 Redis 多方面设计选择的一个综合结果。

一方面,Redis 的大部分操作在内存上完成,再加上它采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。另一方面,就是 Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率。接下来,我们就重点学习下多路复用机制。

首先,我们要弄明白网络操作的基本 IO 模型和潜在的阻塞点。毕竟,Redis 采用单线程进行 IO,如果线程被阻塞了,就无法进行多路复用了。

基本 IO 模型与阻塞点

你还记得我在【第一节课】介绍的具有网络框架的 SimpleKV 吗?

以 Get 请求为例,SimpleKV 为了处理一个 Get 请求,需要监听客户端请求(bind/listen),和客户端建立连接(accept),从 socket 中读取请求(recv),解析客户端发送请求(parse),根据请求类型读取键值数据(get),最后给客户端返回结果,即向 socket 中写回数据(send)。

下图显示了这一过程,其中,bind/listen、accept、recv、parse 和 send 属于网络 IO 处理,而 get 属于键值数据操作。既然 Redis 是单线程,那么,最基本的一种实现是在一个线程中依次执行上面说的这些操作。

Redis基本IO模型

但是,在这里的网络 IO 操作中,有潜在的阻塞点,分别是 accept() 和 recv()。当 Redis 监听到一个客户端有连接请求,但一直未能成功建立起连接时,会阻塞在 accept() 函数这里,导致其他客户端无法和 Redis 建立连接。类似的,当 Redis 通过 recv() 从一个客户端读取数据时,如果数据一直没有到达,Redis 也会一直阻塞在 recv()。

这就导致 Redis 整个线程阻塞,无法处理其他客户端请求,效率很低。不过,幸运的是,socket 网络模型本身支持非阻塞模式。

非阻塞模式

Socket 网络模型的非阻塞模式设置,主要体现在三个关键的函数调用上,如果想要使用 socket 非阻塞模式,就必须要了解这三个函数的调用返回类型和设置模式。接下来,我们就重点学习下它们。

在 socket 模型中,不同操作调用后会返回不同的套接字类型。socket() 方法会返回主动套接字,然后调用 listen() 方法,将主动套接字转化为监听套接字,此时,可以监听来自客户端的连接请求。最后,调用 accept() 方法接收到达的客户端连接,并返回已连接套接字。

Redis套接字类型与非阻塞设置

针对监听套接字,我们可以设置非阻塞模式:当 Redis 调用 accept() 但一直未有连接请求到达时,Redis 线程可以返回处理其他操作,而不用一直等待。但是,你要注意的是,调用 accept() 时,已经存在监听套接字了。

虽然 Redis 线程可以不用继续等待,但是总得有机制继续在监听套接字上等待后续连接请求,并在有请求时通知 Redis。

类似的,我们也可以针对已连接套接字设置非阻塞模式:Redis 调用 recv() 后,如果已连接套接字上一直没有数据到达,Redis 线程同样可以返回处理其他操作。我们也需要有机制继续监听该已连接套接字,并在有数据达到时通知 Redis。

这样才能保证 Redis 线程,既不会像基本 IO 模型中一直在阻塞点等待,也不会导致 Redis 无法处理实际到达的连接请求或数据。

到此,Linux 中的 IO 多路复用机制就要登场了。

基于多路复用的高性能 I/O 模型

Linux 中的 IO 多路复用机制是指一个线程处理多个 IO 流,就是我们经常听到的 select/epoll 机制。简单来说,在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字上的连接请求或数据请求。一旦有请求到达,就会交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。

下图就是基于多路复用的 Redis IO 模型。图中的多个 FD 就是刚才所说的多个套接字。Redis 网络框架调用 epoll 机制,让内核监听这些套接字。此时,Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理上。正因为此,Redis 可以同时和多个客户端连接并处理请求,从而提升并发性。

基于多路复用的Redis高性能IO模型

为了在请求到达时能通知到 Redis 线程,select/epoll 提供了基于事件的回调机制,即针对不同事件的发生,调用相应的处理函数。

那么,回调机制是怎么工作的呢?其实,select/epoll 一旦监测到 FD 上有请求到达时,就会触发相应的事件。

这些事件会被放进一个事件队列,Redis 单线程对该事件队列不断进行处理。这样一来,Redis 无需一直轮询是否有请求实际发生,这就可以避免造成 CPU 资源浪费。同时,Redis 在对事件队列中的事件进行处理时,会调用相应的处理函数,这就实现了基于事件的回调。因为 Redis 一直在对事件队列进行处理,所以能及时响应客户端请求,提升 Redis 的响应性能。

为了方便你理解,我再以连接请求和读数据请求为例,具体解释一下。

这两个请求分别对应 Accept 事件和 Read 事件,Redis 分别对这两个事件注册 accept 和 get 回调函数。当 Linux 内核监听到有连接请求或读数据请求时,就会触发 Accept 事件和 Read 事件,此时,内核就会回调 Redis 相应的 accept 和 get 函数进行处理。

这就像病人去医院瞧病。在医生实际诊断前,每个病人(等同于请求)都需要先分诊、测体温、登记等。如果这些工作都由医生来完成,医生的工作效率就会很低。所以,医院都设置了分诊台,分诊台会一直处理这些诊断前的工作(类似于 Linux 内核监听请求),然后再转交给医生做实际诊断。这样即使一个医生(相当于 Redis 单线程),效率也能提升。

不过,需要注意的是,即使你的应用场景中部署了不同的操作系统,多路复用机制也是适用的。因为这个机制的实现有很多种,既有基于 Linux 系统下的 select 和 epoll 实现,也有基于 FreeBSD 的 kqueue 实现,以及基于 Solaris 的 evport 实现,这样,你可以根据 Redis 实际运行的操作系统,选择相应的多路复用实现。

小结

今天,我们重点学习了 Redis 线程的三个问题:“Redis 真的只有单线程吗?”“为什么用单线程?”“单线程为什么这么快?”

现在,我们知道了,Redis 单线程是指它对网络 IO 和数据读写的操作采用了一个线程,而采用单线程的一个核心原因是避免多线程开发的并发控制问题。单线程的 Redis 也能获得高性能,跟多路复用的 IO 模型密切相关,因为这避免了 accept() 和 send()/recv() 潜在的网络 IO 操作阻塞点。

搞懂了这些,你就走在了很多人的前面。如果你身边还有不清楚这几个问题的朋友,欢迎你分享给他 / 她,解决他们的困惑。

另外,我也剧透下,可能你也注意到了,2020 年 5 月,Redis 6.0 的稳定版发布了,Redis 6.0 中提出了多线程模型。那么,这个多线程模型和这节课所说的 IO 模型有什么关联?会引入复杂的并发控制问题吗?会给 Redis 6.0 带来多大提升?关于这些问题,我会在后面的课程中和你具体介绍。


这期就到这里 , 下期见!

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

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

相关文章

力扣hot100:416.分割等和子集(组合/动态规划/STL问题)

组合数问题 我们思考一下,如果要把数组分割成两个子集,并且两个子集的元素和相等,是否等价于在数组中寻找若干个数使之和等于所有数的一半?是的! 因此我们可以想到,两种方式: ①回溯的方式找到t…

基于SpringBoot+Vue交流和分享平台的设计与实现(源码+部署说明+演示视频+源码介绍)

您好,我是码农飞哥(wei158556),感谢您阅读本文,欢迎一键三连哦。💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精通…

Android Studio实现内容丰富的安卓宠物用品商店管理系统

获取源码请点击文章末尾QQ名片联系,源码不免费,尊重创作,尊重劳动。 项目编号128 1.开发环境android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端: 1.注册登录 2.系统公告 3.宠物社区(可发布宠物帖子&#…

如何将Git拉取项目后,将SSH验证方式修改为HTTPS?

首先在打开项目所在位置的Git BashGUI 查找当前的远程仓库URL: 打开终端或命令提示符,导航到你的项目目录,并使用以下命令查看当前配置的远程仓库URL: git remote -v这会显示如下格式的输出: origin gitgithub.com:用…

从政府工作报告探讨计算机行业的发展

从政府工作报告探计算机行业发展 政府工作报告作为政府工作的全面总结和未来规划,不仅反映了国家整体的发展态势,也为各行各业提供了发展的指引和参考。随着信息技术的快速发展,计算机行业已经成为推动经济社会发展的重要引擎之一。因此&…

什么是 KNIME Hub(2024)

什么是 KNIME Hub KNIME Hub 是一个中央存储库和协作平台,它是用来促进与 KNIME Analytics Platform(分析平台,AP)相关的工作流、节点、组件和扩展的共享和管理。它既充当工作流存储库又充当协作空间,使用户能够发现和利用可合并到其数据分析项目中的各种…

前端Prettier 插件的使用配置(详细)

各个参数代表的意思:printWidth:每行代码的最大长度限制。 tabWidth:选项用于控制制表符的宽度。 useTabs:指定是否使用制表符代替空格。 semi:指定是否在语句的末尾添加分号。 singleQuote:指定是否使用单引号或双引号…

控制学习_正弦波无刷直流力矩电机建模、控制带宽讨论与选择

无刷电机通过电子换向器实现定子的磁场旋转,去电刷后使用寿命大幅提升,是现在更流行的选择。三相无刷电机则是无刷电机中比较流行的一款。三相无刷电机的驱动方式有多种,最简单的被称为梯形波驱动、方波驱动或正弦波驱动。而正弦波驱动技术可…

Redis中的HyperLogLog以及HyperLogLog原理

大家在学习redis的过程中,除了String,list,hash,set,zset这五种基本的数据结构,一定还会接触到几种高级的数据结构,比如bitmap,geo, 还有今天我们要说的hyperloglog&…

【OJ】string类题目

个人主页 : zxctscl 如有转载请先通知 题目 1. 415字符串相加1.1 分析1.2 代码 2. 344反转字符串2.1 分析2.2 代码 3. HJ1字符串最后一个单词的长度3.1 分析3.2 代码 4. 387.字符串中的第一个唯一字符4.1 分析4.2 代码 5. 125验证回文串5.1 分析5.2 代码 1. 415字符…

ctf-web23

web23 substr substr:字符串截取;substr()函数可以用于字符串处理、数据清洗、数据挖掘等领域。 substr(abcdef,2,2)返回值cd PHP intval() 函数 ​编辑PHP 可用的函数 intval() 函数用于获取变量的整数值。 intval() 函数通过使用指定的进制 base 转换&…

2000-2021年各省外商直接投资水平面板数据(含原始数据+计算结果)(无缺失)

2000-2021年各省外商直接投资水平面板数据(含原始数据计算结果)(无缺失) 1、时间:2000-2021年 2、指标:外商直接投资额(万美元)、外商直接投资额(万元)、国…

mysql 更新时,旧值与新值相同会怎么做?

文章目录 1 问题描述2 验证2.1 验证猜想12.2 验证猜想2 3 结论4 mysql 为什么这么设计呢? 1 问题描述 创建一张表t,插入一行数据 mysql> CREATE TABLE t ( id int(11) NOT NULL primary key auto_increment, a int(11) DEFAULT NULL ) ENGINEInnoDB…

01——LenNet网络结构,图片识别

目录 1、model.py文件 (预训练的模型) 2、train.py文件(会产生训练好的.th文件) 3、predict.py文件(预测文件) 4、结果展示: 1、model.py文件 (预训练的模型) impor…

c语言的字符串函数详解

文章目录 前言一、strlen求字符串长度的函数二、字符串拷贝函数strcpy三、链接或追加字符串函数strcat四、字符串比较函数strcmp五、长度受限制字符函数六、找字符串2在字符串1中第一次出现的位置函数strstr七、字符串切割函数strtok(可以切割分隔符)八、…

基于springboot实现酒店客房管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现酒店客房管理平台系统演示 摘 要 随着人们的物质水平的提高,旅游业和酒店业发展的速度越来越快。近年来,市面上酒店的数量和规模都在不断增加,如何提高酒店的管理效率和服务质量成为了一个重要的问题。伴随着信息技术的发…

CSS中如何设置单行或多行内容超出后,显示省略号

1. 设置超出显示省略号 css设置超出显示省略号可分两种情况: 单行文本溢出显示省略号…多行文本溢出显示省略号… 但使用的核心代码是一样的:需要先使用 overflow:hidden;来把超出的部分隐藏,然后使用text-overflow:ellipsis;当文本超出时…

mybatis源码阅读系列(一)

源码下载 mybatis 初识mybatis MyBatis 是一个优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的…

JDK8和JDK11在Ubuntu18上切换(解决nvvp启动报错)

本文主要介绍JDK8和JDK11在Ubuntu18上切换,以供读者能够理解该技术的定义、原理、应用。 🎬个人简介:一个全栈工程师的升级之路! 📋个人专栏:计算机杂记 🎀CSDN主页 发狂的小花 🌄人…

docker login 阿里云失败??

docker login 阿里云失败?? 首先参考 阿里云官方文档《Docker登录、推送和拉取失败常见问题》 看看是否是下面提到的情况: 我遇到的情况是超时: [rootk8snode1 software]# sudo docker login --usernametyleryun registry.cn-hangzhou.ali…