【Redis】Redis线程模型

news2024/12/25 9:00:49

目录

    • 1. Redis 是单线程的,还是多线程的?
    • 2. Redis单线程模式是怎么样的?
      • Redis 单线程模式的优势
      • Redis 单线程的局限性
      • Redis 单线程的优化策略
    • 3. Redis采用单线程为什么还这么快
    • 4. Redis 6.0 之前为什么使用单线程?
    • 5. Redis 6.0 之后为何引入多线程?

1. Redis 是单线程的,还是多线程的?

Redis 的主要操作是单线程的,这意味着它在主线程上处理所有请求,而不像一些数据库采用多线程模型。这是因为 Redis 的开发者追求的是简化并发操作、避免线程锁机制带来的复杂性和性能开销。通过单线程模型,Redis 避免了常见的线程切换、死锁等问题,大大提升了效率。

Redis 单线程指的是「接收客户端清求->解析请求 ->进行数据读写等操作->发送数据给客户端」这个过
程是由一个线程(主线程)来完成的,这也是我们常说 Redis 是单线程的原因。对于读写命令来说,Redis 一直是单线程模型。不过,在 Redis 4.0 版本之后引入了多线程来执行一些大键值对的异步删除操作, Redis6.0版本之后引入了多线程来处理网络请求(提高网络 I0 读写性能)

但是,Redis 程序并不是仅仅是单线程的,Redis 在启动的时候,是会启动后台线程(BIO)的:

  • Redis 在 2.6 版本,会启动2个后台线程,分别处理关闭文件、AOF 刷盘这两个任务;
  • Redis 在 4.0 版本之后,新增了一个新的后台线程,用来异步释放 Redis 内存,也就是 lazyfree 线程。例如执行 unlink key/flushdb async/flushall async 等命令,会把这些删除操作交给后台线程来执行好处是不会导致 Redis 主线程卡顿。因此,当我们要删除一个大 key 的时候,不要使用 del 命令删除因为 del是在主线程处理的,这样会导致 Redis 主线程卡顿,因此我们应该使用 unlink 命令来异步删除大key

之所以 Redis 为「关闭文件、AOF 刷盘、释放内存」这些任务创建单独的线程来处理,是因为这些任务的操作都是很耗时的,如果把这些任务都放在主线程来处理,那么 Redis 主线程就很容易发生阻塞,这样就无法处理后续的请求了。
在这里插入图片描述

后台线程相当于一个消费者,生产者把耗时任务丢到任务队列中,消费者(BIO)不停轮询这个队列,拿出任务就去执行对应的方法即可。

2. Redis单线程模式是怎么样的?

Redis6.0版本之前的单线程模式如下图:(图片来源小林coding)
在这里插入图片描述在 Redis 6.0 版本之前,Redis 的处理模型完全是 单线程 的,所有操作都在一个线程中完成。这种单线程模式虽然看似简单,但由于 Redis 的设计精巧,结合了高效的内存操作和 I/O 多路复用机制,它依然能够在很多场景下表现出色。

Redis 使用 I/O 多路复用机制 来处理并发客户端连接。在这种机制下,一个线程可以同时监听多个文件描述符(即客户端连接)。当某个文件描述符有事件发生(如客户端发送了命令或等待处理的请求到来),Redis 会处理这些事件,而不是为每个连接创建一个独立的线程。

具体的步骤如下:

1. 事件监听:Redis 的主线程通过 I/O 多路复用(如 epoll、select 等系统调用)监听多个客户端连接。当某个连接有新的请求到来时,Redis 会记录这个事件。
2. 事件处理:当有多个客户端的事件需要处理时,Redis 按照队列的顺序处理每个请求。这意味着 Redis 通过单线程的轮询方式来处理每一个客户端请求。
3. 命令执行:Redis 按顺序处理请求,包括读取命令、执行命令、返回结果等所有操作都在主线程中完成。

Redis 单线程模式的优势

  1. 避免复杂的线程同步问题

Redis 采用单线程设计的一个主要原因是避免了多线程中的复杂同步问题。在多线程环境下,多个线程对共享资源的并发访问需要使用锁机制来保护数据的完整性,但这也会带来性能开销。单线程的 Redis 不需要考虑加锁问题,因为所有的请求都是在同一个线程中按顺序执行,天然避免了竞争条件(race conditions)、死锁和其他并发问题。

  1. 高效的内存操作

Redis 是一个基于内存的数据库,大部分数据操作都是直接在内存中进行的。内存操作的速度非常快,通常在微秒级别。这使得即使 Redis 是单线程的,它在处理绝大多数简单的读写请求时也能够表现得非常高效。

  1. I/O 多路复用

Redis 使用 I/O 多路复用(如 epoll)来处理大量并发连接。I/O 多路复用允许一个线程同时监控多个客户端连接的输入输出,而不会阻塞在某个连接上。这意味着,虽然 Redis 是单线程,但它依然可以高效地处理成千上万的并发连接。

非阻塞 I/O:Redis 的网络操作是非阻塞的,这意味着即便某个连接上没有立即准备好数据,Redis 也不会停下来等待,而是继续处理其他连接上的请求。

事件驱动模型:Redis 使用事件驱动模型来处理网络 I/O,即当某个连接上有新的事件(如数据可读、可写等)时,Redis 会立即响应并处理这个事件。这样,Redis 不需要为每个连接分配单独的线程,而是通过事件驱动的方式让单线程高效运作。

Redis 单线程的局限性

虽然 Redis 单线程模式在大多数场景下表现良好,但也存在一些局限性,特别是在特定条件下可能成为瓶颈:

  1. CPU 密集型任务

虽然 Redis 的大部分操作都是非常轻量的,但在某些 CPU 密集型任务中,如处理非常大的集合或执行复杂的 Lua 脚本,单线程的性能可能会不足。当 Redis 需要在主线程中执行耗时较长的计算时,其他客户端的请求可能会因此被阻塞。

  1. 大数据量的磁盘 I/O

当 Redis 执行持久化操作时(如将数据写入 AOF 文件或 RDB 文件),写入磁盘是一个相对慢的操作。虽然 Redis 通过异步机制和后台线程来优化某些持久化任务(如 AOF 重写和 RDB 文件生成),但在 6.0 之前版本中,网络 I/O 和命令处理仍然完全依赖主线程,因此在高并发场景下,磁盘 I/O 可能成为性能瓶颈。

  1. 处理大型数据集的阻塞问题

对于像删除大量数据(如一个包含数百万个元素的哈希或列表)或从主线程直接进行内存清理这样的操作,单线程可能会导致 Redis 短暂阻塞,影响其他请求的处理。为此,Redis 提供了 UNLINK 等命令来将大型对象的删除任务交由后台线程异步处理,减少主线程的负载。

Redis 单线程的优化策略

为了解决单线程的性能瓶颈,Redis 在 6.0 之前引入了一些优化策略:

异步删除:通过命令 UNLINK 和 FLUSHALL/FLUSHDB ASYNC,Redis 能够将大数据块的删除操作交由后台线程异步执行,从而避免删除大对象时主线程的阻塞。

Lazy-Free 机制:Redis 提供了“惰性删除”机制,允许用户在删除大对象时,分步释放对象占用的内存,而不是一次性完成,从而减小对主线程的影响。

后台持久化:对于 RDB 生成和 AOF 重写操作,Redis 通过后台子进程完成。这些操作涉及到磁盘 I/O,通常比较耗时,但通过子进程执行,避免了主线程的阻塞。

总结:

Redis 6.0 版本之前的单线程模式基于 I/O 多路复用和事件驱动模型,能够高效处理大量并发请求。它的优点在于实现简单、避免复杂的并发问题,同时依赖内存操作和非阻塞 I/O,保证了大多数场景下的高性能表现。

然而,在 CPU 密集型任务、大数据集操作和磁盘 I/O 瓶颈等场景下,单线程模式可能会遇到一些性能瓶颈。因此,在 Redis 6.0版本中,引入了多线程来优化网络 I/O 的处理能力,从而进一步提升 Redis 在高并发场景下的性能表现。

3. Redis采用单线程为什么还这么快

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

在这里插入图片描述Redis 采用单线程(处理网络 I/O 和执行命令)却依然能够保持极高的性能,主要有以下几个原因:

  1. 基于内存的操作

Redis 是一个 内存数据库,所有数据操作都直接在内存中完成。相比传统的磁盘数据库,内存读写速度极快,通常在微秒级别。这使得 Redis 能够非常迅速地处理请求,减少了 I/O 等待时间。

  1. I/O 多路复用机制

Redis 使用 I/O 多路复用(如 epoll、kqueue 或 select)来同时处理多个客户端连接。尽管 Redis 是单线程的,但它能够高效地处理大量并发连接,因为它不需要为每个连接创建单独的线程或进程,而是通过事件驱动的方式监听和处理多个连接上的请求。多路复用允许 Redis 处理大量并发请求的能力远超一般的单线程程序。

  1. 非阻塞 I/O

Redis 采用 非阻塞 I/O 模式,主线程不会因为某个 I/O 操作(如等待客户端请求数据或写入响应)而被卡住。即使某个客户端数据没有完全到达,Redis 也不会阻塞,而是先处理其他已经准备好的请求,保证主线程的连续执行和高效处理。

  1. 单线程避免了锁的开销

多线程编程通常需要处理线程之间的同步问题,比如加锁、解锁等操作,这些都会引入性能开销。Redis 的单线程模型完全避免了这些复杂性,因为所有的操作都是按顺序执行的,不需要使用锁来保证数据一致性。因此,Redis 能够避免常见的多线程编程中的资源竞争、锁定和上下文切换带来的性能损失。

  1. 简单高效的事件驱动模型

Redis 的事件驱动模型设计简洁,主线程通过事件循环不断监听并处理新的客户端连接、请求和响应。这种模型非常轻量化,减少了上下文切换和系统调用的开销,使得 Redis 在处理大量连接时依然保持高效。

  1. 高效的数据结构

Redis 的数据结构设计非常高效,它在内存中使用了优化的编码方式(如 ziplist、intset 等)来存储数据。这些数据结构专为快速访问和操作而设计,使得 Redis 可以在单线程模式下快速执行各种数据操作。

4. Redis 6.0 之前为什么使用单线程?

虽然说 Redis 是单线程模型,但实际上,Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。不过,Redis 4.0增加的多线程主要是针对一些大键值对的删除操作的命令,使用这些命令就会使用主线程之外的其他线程来“异步处理”,从而减少对主线程的影响。

Redis官方的F&Q
在这里插入图片描述在这里插入图片描述
使用单线程的原因:

  • 单线程编程容易并且更容易维护;
  • Redis的性能瓶颈不在 CPU,主要在内存和网络IO;
  • 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。

5. Redis 6.0 之后为何引入多线程?

虽然 Redis 的主要工作(网络 I/O和执行命令)一直是单线程模型,但是在 Redis 6.0 版本之后,也采用多个IO线程来处理网络请求,这是因为随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 I/O 的处理上

所以为了提高网络 I/O 的并行度,Redis 6.0 对于网络 I/O 采用多线程来处理。但是对于命令的执行,Redis 仍然使用单线程来处理,所以不要误解 Redis 有多线程同时执行命令。

Redis 6.0 版本支持的 I/O 多线程特性,默认情况下 I/O 多线程只针对发送响应数据(write client socket),并不会以多线程的方式处理读请求(read client socket)。要想开启多线程处理客户端读请求,就需要把 Redis.conf 配置文件中的 io-threads-do-reads 配置项设为 yes。

//读请求也使用io多线程
io-threads-do-reads yes 

同时, Redis.conf 配置文件中提供了IO 多线程个数的配置项。

// io-threads ,表示启用 N-1 个 I/0 多线程(主线程也算一个 I/0 线程)
io-threads 4

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

因此, Redis 6.0 版本之后,Redis 在启动的时候,默认情况下会额外创建6个线程(这里的线程数不包括主线程)

  • Redis-server:Redis的主线程,主要负责执行命令;
  • bio_close_file、bio_aof_fsync、bio_lazy_free:三个后台线程,分别异步处理关闭文件任务、AOF刷盘任务、释放内存任务;
  • io_ thd_1、io_thd_2、io_thd_3:三个I/O 线程,io-threads 默认是4,所以会启动3(4-1)个I/O多线程,用来分担 Redis 网络I/O 的压力。

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

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

相关文章

每日学习一个数据结构-图

文章目录 图基础一、图的定义二、图的相关概念三、图的分类四、图的使用场景 和图相关的算法一、图的遍历算法二、最短路径算法三、最小生成树算法四、图匹配算法五、网络流算法 图基础 一、图的定义 在数学中,图是描述于一组对象的结构,其中某些对象对…

数据结构单向链表

单向链表的转置 转置的思想: (1) 将头节点与当前链表断开,断开前保存下头节点的下一个节点,保证后面链表能找得到,定义一个q保存头节点的下一个节点,断开后前面相当于一个空的链表,后面是一个无头的单向链表…

动态内存管理练习题的反汇编代码分析(底层)

1.C语言代码 #include <stdio.h> char* GetMemory(void) {char p[] "hello world";return p; }void Test(void) {char* str NULL;str GetMemory();printf(str); }int main() {Test();return 0; } 2.反汇编代码 VS2022x64debug #include <stdio.h>…

给普通的div或者view等元素添加onblur事件的方式

一般只有input元素有blur事件&#xff0c;但是如果想给普通的元素设置了blur事件之后&#xff0c;它是不会正常直行的。还需要再给元素添加tabindex属性&#xff0c;设置了tabindex属性之后&#xff0c;元素会带有一个下划线&#xff0c;还要去掉下划线&#xff0c;而且聚焦的时…

PostgreSQL学习笔记二:PostgreSQL的系统架构

PostgreSQL 是一种功能强大的开源关系型数据库管理系统&#xff0c;其架构具有以下特点&#xff1a; 一、客户端/服务器架构 客户端 客户端可以是各种应用程序&#xff0c;如 Web 应用、桌面应用等&#xff0c;它们通过网络连接与 PostgreSQL 服务器进行通信。客户端使用标准的…

力扣之603.连续空余座位

文章目录 1. 603.连续空余座位1.1 题干1.2 准备数据1.3 思路分析1.4 解法1.5 结果截图 1. 603.连续空余座位 1.1 题干 表: Cinema ----------------- | Column Name | Type | ----------------- | seat_id | int | | free | bool | ----------------- Seat_id 是该表的自动递…

2024-你自学网络安全的顺序可能一直是反的!

作为一名在网络安全领域工作了八年的技术人员&#xff0c;我想分享一些经验给2024年学习黑客技术的朋友们。 千万不要毫无基础就开始学黑客!一定要先了解相关的信息和知识! 对于刚入行的朋友&#xff0c;我建议先从网络安全或Web安全/渗透测试入手&#xff0c;这些方向市场需求…

【可答疑】基于51单片机的光照强度检测(含仿真、代码、报告、演示视频等)

✨哈喽大家好&#xff0c;这里是每天一杯冰美式oh&#xff0c;985电子本硕&#xff0c;大厂嵌入式在职0.3年&#xff0c;业余时间做做单片机小项目&#xff0c;有需要也可以提供就业指导&#xff08;免费&#xff09;~ &#x1f431;‍&#x1f409;这是51单片机毕业设计100篇…

基于STM32的智能仓库温湿度监控系统设计

引言 本项目设计了一个基于STM32的智能仓库温湿度监控系统&#xff0c;能够实时监测仓库内的温度和湿度&#xff0c;并根据设定的阈值触发报警或启动风扇调节环境。该系统通过DHT11温湿度传感器获取环境数据&#xff0c;结合OLED显示屏、风扇和蜂鸣器&#xff0c;实现对仓库环…

python中的数组模块numpy(一)(适用物联网数据可视化及数据分析)

一、创建数组对象array&#xff0c;认识数组的格式 array函数的格式:np.array(object,dtype,ndmin) 以下是示例代码&#xff1a; # coding:utf-8 import numpy as np d1[1.1,2.1,3.1] d2(1,2,3,4) d3[[a,b],[c,d],[e,f]] print(d1) print(d2) print(d3) print("以上是数…

无法编辑PDF文件?试试这3个解决方法!

PDF文件格式广泛应用于工作中&#xff0c;但有时候我们可能遇到无法编辑PDF文件的情况。这可能导致工作效率降低&#xff0c;特别是在需要修改文件内容时显得尤为棘手。遇到PDF不能编辑时&#xff0c;可以看看是否以下3个原因导致的。 原因一&#xff1a;PDF文件设置了编辑权限…

八大排序--06基数排序(桶排序)

【本质--先排序个位&#xff0c;再排序十位&#xff0c;排百位...依次类推的过程】 获取待排序数组中的最高位数 //取计算最大值的位数int maxarr[0];for(int j0;j<arr.length;j) {if(arr[j]>max) {maxarr[j];}} 存储数据&#xff08;桶排序中除了游标遍历外&#xff…

【数据结构 | PTA】栈

文章目录 7-1 汉诺塔的非递归实现7-2 出栈序列的合法性**7-3 简单计算器**7-4 盲盒包装流水线 7-1 汉诺塔的非递归实现 借助堆栈以非递归&#xff08;循环&#xff09;方式求解汉诺塔的问题&#xff08;n, a, b, c&#xff09;&#xff0c;即将N个盘子从起始柱&#xff08;标记…

分析和解决js运算精度问题,出现多位小数

加减乘除都会出现小数精度错误的问题 (见图) 原因&#xff1a;js进行运算时会将数字先转为二进制再进行运算。 错误思路&#xff1a;之前在做数字运算时都是将数字转化为整数再进行运算&#xff0c;某次突然发现在变整数的时候也会出现精度问题&#xff0c;比如上图中的数据。…

LINUX——内核移植、内核编译教程

Linux内核编译是一个将内核源代码转换成可在特定硬件架构上运行的二进制文件的过程。以下是编译Linux内核的一般步骤&#xff1a; 1、准备工作&#xff1a; 确保安装了必要的编译工具&#xff0c;如gcc、make、ncurses库&#xff08;用于make menuconfig&#xff09;等。 2、…

【读书笔记-《30天自制操作系统》-26】Day27

本篇内容不多&#xff0c;主要是一些优化的工作。首先优化了应用程序&#xff0c;然后引入对应用程序的保护功能&#xff0c;最后引入库的概念。 1. 应用程序优化 首先来解决上一篇中遗留的一个bug:使用ncst命令运行的应用程序&#xff0c;按下ShiftF1或者点击x按钮都无法关…

Hierarchical Cross-Modal Agent for Robotics Vision-and-Language Navigation

题目&#xff1a;用于视觉语言导航的层次化跨模态智能体 摘要 1. 问题背景和现有方法 VLN任务&#xff1a;这是一种复杂的任务&#xff0c;要求智能体基于视觉输入和自然语言指令进行导航。 现有方法的局限性&#xff1a;之前的工作大多将这个问题表示为离散的导航图&#x…

『网络游戏』登陆启动框架【05】

将上一章的加载界面隐藏 1.游戏启动逻辑 创建脚本GameRoot.cs &#xff08;该脚本为游戏入口&#xff0c;作用初始化游戏&#xff09; 创建脚本&#xff1a;ResSvc.cs &#xff08;&#xff09; 创建脚本&#xff1a;LoginSys.cs &#xff08;&#xff09; 编写脚本&a…

解析Vue源码中是如何进行模版编译的

模版编译 联系前文&#xff0c;讲了虚拟DOM的patch过程&#xff0c;而虚拟DOM的前提是先有VNode&#xff0c;那么VNode又是从哪里来的&#xff1f;接下来讲的模版编译便是&#xff1a;把用户写的模版进行编译&#xff0c;就会产生VNode。 在日常开发中&#xff0c;我们把写在…

Qt-目录和文件

1. 目录和文件 1.1 目录操作 QDir 类用来处理目录 常用方法&#xff1a; QDir(QString path) &#xff1a; 实例化 absolutePath() : 获取目录绝对路径 dirName() : 获取目录相对路径 exists(dirPath) : 判断目录是否存在 mkdir(QString dirPath) : 创建目录 rmdir(QStr…