当前位置: 首页 > news >正文

【Redis】Redis线程模型

目录

    • 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 的压力。

相关文章:

【Redis】Redis线程模型

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

Electron构建桌面应用程序,服务于项目的自主学习记录(持续更新...

无所畏惧地面对未知,并将其视为成长的机会 大纲官网快速入门1.安装node.js -- 这里推荐用nvm管理2.脚手架创建3.electron 包安装到应用的开发依赖4.创建主进程(main.js)并启动项目1.创建页面2.配置main.js3.启动项目 -- 效果 进阶 -- 基于项目场景功能使用场景一&am…...

linux Load Average 计算

在内核代码 kernel/sched/loadavg.c 中有一个公式: a1 a0 * e a * (1 - e) 此算法是指数加权移动平均法(Exponential Weighted Moving Average,EMWA),是一种特殊的加权移动平均法,它考虑当前和历史的所有数据&#…...

pandas常用数据格式IO性能对比

前言 本文对pandas支持的一些数据格式进行IO(读写)的性能测试,大数据时代以数据为基础,经常会遇到操作大量数据的情景,数据的IO性能尤为重要,本文对常见的数据格式csv、feather、hdf5、jay、parquet、pick…...

【D3.js in Action 3 精译_031】3.5.2 DIY实战:在 Observable 平台实现带数据标签的 D3 条形图并改造单元测试模块

当前内容所在位置(可进入专栏查看其他译好的章节内容) 第一部分 D3.js 基础知识 第一章 D3.js 简介(已完结) 1.1 何为 D3.js?1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践(上)1.3 数据可…...

华为OD机试真题-字符串分割

题目描述: 给定非空字符串s,将该字符串分割成一些子串,使每个子串的ASCII码值的和均为水仙花数。 1、若分割不成功,则返回0。 2、若分割成功且分割结果不唯一,则返回-1。 3、若分割成功且分割结果唯一,则返…...

编程技巧:提高代码健壮性与可维护性的关键方法(以 Shell 为例)

在脚本编写和自动化工作中,良好的编程技巧对于确保代码的健壮性和可维护性至关重要。以下是一些关键的编程技巧,包括模块化设计、单元测试、版本控制、处理边界条件、错误处理、中间值保存和创建 Flag。本文将通过 Shell 脚本示例来阐述这些技巧的应用。 1. 模块化设计 **定…...

【无标题】ReadableStream is not defined

升级 node 版本到 18 及以上即可解决...

【JVM】高级篇

1 GraalVM 1.1 什么是GraalVM GraalVM是Oracle官方推出的一款高性能JDK,使用它享受比OpenJDK或者OracleJDK更好的性能。 GraalVM的官方网址:https://www.graalvm.org/ 官方标语:Build faster, smaller, leaner applications。 更低的CPU…...

nacos1.4源码-服务发现、心跳机制

nacos的服务发现主要采用服务端主动推送客户端定时拉取;心跳机制通过每5s向服务端发送心跳任务来保活,当超过15s服务端未接收到心跳任务时,将该实例设置为非健康状态;当超过30s时,删除该实例。 1.服务发现 nacos主要采…...

C++ 2D平台游戏开发案例

关于2D平台游戏的C开发案例,包括游戏设计、实现细节、图形渲染和音效处理等内容。虽然无法一次性提供3000字,但我会尽量详细描述各个部分,并确保有足够的深度和广度。 2D平台游戏开发案例 一、游戏设计 游戏概述 游戏名称:“冒险…...

【Webpack--019】TreeShaking

🤓😍Sam9029的CSDN博客主页:Sam9029的博客_CSDN博客-前端领域博主 🐱‍🐉若此文你认为写的不错,不要吝啬你的赞扬,求收藏,求评论,求一个大大的赞!👍* &#x…...

Docker基本操作命令

Docker 是一个开源的应用容器引擎,允许开发者打包应用以及其依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。主要功能是为开发者提供一个简单…...

开源计算器应用的全面测试计划:确保功能性和可靠性

✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...

uni.requestPayment 支付成功之后会走 wx.onAppRoute

uni.requestPayment 是用于发起微信支付的统一接口,而 wx.onAppRoute 是用于监听小程序的路由变化。当 uni.requestPayment 支付成功后,如果发生了页面跳转或者其他路由变化,wx.onAppRoute 会被触发。这个行为是正常的,因为支付成…...

统⼀服务入口 - Gateway

网关介绍 问题 在 spring cloud 体系中我们通过 Eureka,Nacos 解决了服务注册,服务发现的问题,使⽤Spring Cloud LoadBalance解决了负载均衡的问题,使⽤ OpenFeign 解决了远程调⽤的问题. 但是当前所有微服务的接⼝都是直接对外暴露的,可以直接通过外部访问.为了保证对外服务的…...

QGraphicsWidget Class

Header:#include < QGraphicsWidget > qmake:QT += widgets Since:Qt 4.4 Inherits:QGraphicsObject and QGraphicsLayoutItem Inherited By:QGraphicsProxyWidget This class was introduced in Qt 4.4. Public Types enum anonymous {Type }Properties autoFi…...

探讨最好用的AI工具:从日常到创新的应用

文章目录 引言常用AI工具1. 语音助手2. 图像识别软件3. 机器翻译工具4. 智能客服系统 创新AI应用1. 自动驾驶汽车2. 虚拟试衣间3. 医疗影像分析4. 个性化推荐系统 个人体验分享1. 通义灵码2. 文心一言3. 智能写作助手4. 智能家居设备5. DALLE6. Whisper7. Codex8. Gym9. ChatGP…...

Python系统教程005(字符串的格式化输出)

知识回顾 1、默认情况下&#xff0c;input函数接收的数据是字符串类型。 2、字符串类型的关键词是str。 3、\n和\t都是转义字符&#xff0c;\n用来换行&#xff0c;\t用来留出一段固定长度的空白。 4、type函数能够用来查看变量的数据类型 5、数据类型的转换&#xff0c;举…...

六款电脑远程控制软件分享,2024最热门软件合集,总有一款适合你!速来看!

想要随时随地控制自己的电脑&#xff1f; 无论你是办公需求&#xff0c;还是要远程协助他人&#xff0c;一款好用的远程控制软件绝对少不了。 2024年最热门的六款远程控制软件已经为你准备好&#xff0c;总有一款适合你&#xff0c;赶快往下看吧&#xff01; 1. 安企神系统—…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

C# SqlSugar:依赖注入与仓储模式实践

C# SqlSugar&#xff1a;依赖注入与仓储模式实践 在 C# 的应用开发中&#xff0c;数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护&#xff0c;许多开发者会选择成熟的 ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;SqlSugar 就是其中备受…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习

禁止商业或二改转载&#xff0c;仅供自学使用&#xff0c;侵权必究&#xff0c;如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...

Mysql中select查询语句的执行过程

目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析&#xff08;Parser&#xff09; 2.4、执行sql 1. 预处理&#xff08;Preprocessor&#xff09; 2. 查询优化器&#xff08;Optimizer&#xff09; 3. 执行器…...

Go 并发编程基础:通道(Channel)的使用

在 Go 中&#xff0c;Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式&#xff0c;用于在多个 Goroutine 之间传递数据&#xff0c;从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

【Linux】Linux 系统默认的目录及作用说明

博主介绍&#xff1a;✌全网粉丝23W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...