探索Redis是否为单线程的奥秘(文末送书)

在这里插入图片描述
🌈个人主页:聆风吟
🔥系列专栏:数据结构、网络奇遇记
🔖少年有梦不应止于心动,更要付诸行动。


文章目录

  • 📋前言
  • 一. Redis中的多线程
  • 二. I/O多线程
  • 三. Redis中的多进程
  • 四. 结论
  • 五. 书籍推荐
    • 5.1 书籍介绍
    • 5.2 作者简介
    • 5.3 粉丝福利

参与活动方式文末详见。

📋前言

很多人都遇到过这么一道面试题:Redis是单线程还是多线程?这个问题既简单又复杂。说他简单是因为大多数人都知道Redis是单线程,说复杂是因为这个答案其实并不准确。

难道Redis不是单线程?我们启动一个Redis实例,验证一下就知道了。Redis安装部署方式如下所示:

// 下载
wget https://download.redis.io/redis-stable.tar.gz
tar -xzvf redis-stable.tar.gz

// 编译安装
cd redis-stable
make

// 验证是否安装成功
./src/redis-server -v
Redis server v=7.2.4

接下来启动Redis实例,使用命令ps查看所有线程,如下所示:

// 启动Redis实例
./src/redis-server ./redis.conf

// 查看实例进程ID
ps aux | grep redis
root     385806  0.0  0.0 245472 11200 pts/2    Sl+  17:32   0:00 ./src/redis-server 127.0.0.1:6379

// 查看所有线程
ps -L -p 385806
   PID    LWP TTY          TIME CMD
385806 385806 pts/2    00:00:00 redis-server
385806 385809 pts/2    00:00:00 bio_close_file
385806 385810 pts/2    00:00:00 bio_aof
385806 385811 pts/2    00:00:00 bio_lazy_free
385806 385812 pts/2    00:00:00 jemalloc_bg_thd
385806 385813 pts/2    00:00:00 jemalloc_bg_thd

竟然有6个线程!不是说Redis是单线程吗?怎么会有这么多线程呢?

这6个线程的含义你可能不太了解,但是通过这个示例至少说明Redis并不是单线程。



一. Redis中的多线程

接下来我们逐个介绍上述6个线程的作用:

  1. redis-server:
    主线程,用于接收并处理客户端请求。

  2. jemalloc_bg_thd:
    jemalloc 是新一代的内存分配器,Redis底层使用他管理内存。

  3. bio_xxx:
    以bio前缀开始的都是异步线程,用于异步执行一些耗时任务。其中,线程bio_close_file用于异步删除文件,线程bio_aof用于异步将AOF文件刷到磁盘,线程bio_lazy_free用于异步删除数据(懒删除)。

需要说明的是,主线程是通过队列将任务分发给异步线程的,并且这一操作是需要加锁的。主线程与异步线程的关系如下图所示:
在这里插入图片描述
这里我们以懒删除为例,讲解为什么要使用异步线程。Redis是一款内存数据库,支持多种数据类型,包括字符串、列表、哈希表、集合等。思考一下,删除(DEL)列表类型数据的流程是怎样的呢?第一步从数据库字典中删除该键值对,第二步遍历并删除列表中的所有元素(释放内存)。想想如果列表中的元素数目非常多呢?这一步将非常耗时。这种删除方式称为同步删除,流程如下图所示:
在这里插入图片描述

针对上述问题,Redis提出了懒删除(异步删除),主线程在收到删除命令(UNLINK)时,首先从数据库字典中删除该键值对,随后再将删除任务分发给异步线程bio_lazy_free,由异步线程执行第二步耗时逻辑。这时候的流程如下图所示:
在这里插入图片描述



二. I/O多线程

难道Redis是多线程?那为什么我们老说Redis是单线程呢?这是因为读取客户端命令请求,执行命令以及向客户端返回结果都是在主线程完成的。不然的话,多线程同时操作内存数据库,并发问题如何解决?如果每次操作之前都加锁,那和单线程又有什么区别呢?

当然这一流程在Redis6.0版本也发生了改变,Redis官方指出,Redis是基于内存的键值对数据库,执行命令的过程是非常快的,读取客户端命令请求和向客户端返回结果(即网络I/O)通常会成为Redis的性能瓶颈。

因此,在Redis 6.0版本,作者加入了多线程I/O的能力,即可以开启多个I/O线程,并行读取客户端命令请求,并行向客户端返回结果。I/O多线程能力使得Redis性能提升至少一倍。

为了开启多线程I/O能力,需要先修改配置文件redis.conf:

io-threads-do-reads yes
io-threads 4

这两个配置含义如下:

  • io-threads-do-reads:是否开启多线程I/O能力,默认为"no";

  • io-threads:I/O线程数目,默认为1,即只使用主线程执行网络I/O,线程数最大为128;该配置应该根据CPU核数设置,作者建议,4核CPU设置2~3个I/O线程,8核CPU设置6个I/O线程。

开启多线程I/O能力之后,重新启动Redis实例,查看所有线程,结果如下:

ps -L -p 104648
   PID    LWP TTY          TIME CMD
104648 104648 pts/1    00:00:00 redis-server
104648 104654 pts/1    00:00:00 io_thd_1
104648 104655 pts/1    00:00:00 io_thd_2
104648 104656 pts/1    00:00:00 io_thd_3
……

由于我们设置了io-threads等于4,所以会创建4个线程用于执行I/O操作(包括主线程),上述结果符合预期。

当然,只有I/O阶段才使用了多线程,处理命令请求还是单线程,毕竟多线程操作内存数据存在并发问题。

最后,开启了I/O多线程之后,命令的执行流程如下图所示:
在这里插入图片描述



三. Redis中的多进程

Redis还有多进程?是的。在某些场景下,Redis也会创建多个子进程来执行一些任务。以持久化为例,Redis支持两种类型的持久化:

  • AOF(Append Only File):可以看作是命令的日志文件,Redis会将每一个写命令都追加到AOF文件。

  • RDB(Redis Database):以快照的方式存储Redis内存中的数据。命令SAVE用于手动触发RDB持久化。想想如果Redis中的数据量非常大,持久化操作必然耗时比较长,而Redis是单线程处理命令请求,那么当命令SAVE的执行时间过长时,必然会影响其他命令的执行。

命令SAVE有可能会阻塞其他请求,为此,Redis又引入了命令BGSAVE,该命令会创建一个子进程来执行持久化操作,这样就不会影响主进程执行其他请求了。

我们可以手动执行命令BGSAVE验证。首先,使用GDB跟踪Redis进程,添加断点,让子进程阻塞在持久化逻辑。如下所示:

// 查询Redis进程ID
ps aux | grep redis
root     448144  0.1  0.0 270060 11520 pts/1    tl+  17:00   0:00 ./src/redis-server 127.0.0.1:6379

// GDB跟踪进程
gdb -p 448144

// 跟踪创建的子进程(默认GDB只跟踪主进程,需手动设置)
(gdb) set follow-fork-mode child

// 函数rdbSaveDb用于持久化数据快照
(gdb) b rdbSaveDb
Breakpoint 1 at 0x541a10: file rdb.c, line 1300.
(gdb) c

设置好断点之后,使用Redis客户端发送命令BGSAVE,结果如下:

// 请求立即返回
127.0.0.1:6379> bgsave
Background saving started

// GDB输出以下信息
[New process 452541]
Breakpoint 1, rdbSaveDb (...) at rdb.c:1300

可以看到,GDB目前跟踪的是子进程,进程ID是452541。也可以通过Linux命令 ps 查看所有进程,结果如下:

ps aux | grep redis
root     448144  0.0  0.0 270060 11520 pts/1    Sl+  17:00   0:00 ./src/redis-server 127.0.0.1:6379
root     452541  0.0  0.0 270064 11412 pts/1    t+   17:19   0:00 redis-rdb-bgsave 127.0.0.1:6379

可以看到子进程的名称是redis-rdb-bgsave,也就是该进程将所有数据的快照持久化在RDB文件。

最后再思考两个问题。
问题1:为什么采用子进程而不是子线程呢?
    因为RDB是将数据快照持久化存储,如果采用子线程,主线程与子线程将会共享内存数据,主线程在持久化的同时还会修改内存数据,这有可能导致数据不一致。而主进程与子进程的内存数据是完全隔离的,不存在此问题。

问题2:假设Redis内存中存储了10GB的数据,在创建子进程执行持久化操作之后,此时子进程也需要10GB的内存吗?复制10GB的内存数据,也会比较耗时吧?另外如果系统只有15GB的内存,还能执行BGSAVE命令吗?
    这里有一个概念叫写时复制(copy on write),在使用系统调用fork创建子进程之后,主进程与子进程的内存数据暂时还是共享的,但是当主进程需要修改内存数据时,系统会自动将该内存块复制一份,以此实现内存数据的隔离。
命令BGSAVE的执行流程如下图所示:
在这里插入图片描述



四. 结论

Redis的进程模型/线程模型还是比较复杂的,这里也只是简单介绍了部分场景下的多线程以及多进程,其他场景下的多线程、多进程还有待读者自己研究。



五. 书籍推荐

5.1 书籍介绍

在这里插入图片描述
全书主要分为三部分介绍Redis。

  • 第一部分介绍Redis6中使用的数据结构,包括动态字符串、跳跃表、压缩列表、字典、整数集合和快速链表,详细介绍其基本结构及常见操作。
  • 第二部分为本书核心篇章,首先介绍了Redis6的启动流程,命令解析流程,之后对Redis6中的命令实现进行了全面的介绍,包括键命令、字符串命令、哈希表命令、列表命令、集合及有序集合命令、地理位置相关的GEO命令、统计相关的HyperLogLog命令。
  • 第三部分,主要介绍了Redis6的一些特性及使用,包括事务、持久化、主从复制以及集群等。

5.2 作者简介

李乐:好未来Golang开发专家、西安电子科技大学硕士,曾就职于滴滴,乐于钻研技术与源码,合著有《高效使用Redis:一书学透数据存储与高可用集群》《Redis5设计与源码分析》《Nginx底层设计与源码分析》。


5.3 粉丝福利

送书规则:

  • 参与方式:关注博主、点赞、收藏、评论(每人最多评论三次)

  • ⛳️本次送书1~5本【取决于阅读量,阅读量越多,送的越多】

  • 📆 活动截止时间:2024-2-27 19:00:00 | 由博主动态公布抽奖结果

🔥注:活动结束后,会私信中奖粉丝的,各位注意查看私信哦!
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CoordConv(NeurIPS 2018)

paper:An Intriguing Failing of Convolutional Neural Networks and the CoordConv Solution official implementation:https://github.com/uber-research/coordconv 存在的问题 本文揭示并分析了CNN在两种不同类型空间表示之间转换能力的欠缺&#…

BLUEZ学习笔记_GATT_server_client_简单解析

文章参考了以下内容 蓝牙bluez5的开发方法及入门教程_bluez蓝牙配网demo-CSDN博客文章浏览阅读1w次,点赞15次,收藏99次。1 摘要这篇文章的主要目的是告诉大家应该如何使用bluez进行开发,由于bluez的文档实在太少了,入门门槛实在太…

vue保留用户在列表的操作记录, beforeRouteLeave离开当前组件缓存数据即可

最近遇到一个需求,用户在列表页的查询输入框输入条件后,点击查询,然后此时切换菜单,再回到之前的页面,希望能停留在上一次输入的结果上,如下例子,用户管理页面,输入yangfan这个关键词搜索后,结果如下图: 当我此时点击权限管理后,再点击用户管理切回来,结果依旧如上…

(二十二)Flask之上下文管理第三篇【收尾—讲一讲g】

目录: 每篇前言:g到底是什么?生命周期在请求周期内保持数据需要注意的是: 拓展—面向对象的私有字段深入讲解一下那句: 每篇前言: 🏆🏆作者介绍:【孤寒者】—CSDN全栈领域…

ubuntu内核卸载重装

目录 问题1.问题复现2.可以正常启动的方式 保存快照卸载有问题的内核重装最新内核参考资料 问题 1.问题复现 ubuntu开机出现如下画面,启动不能正常启动 2.可以正常启动的方式 使用其他内核可以正常工作 保存快照 在解决之前保存快照,防止破坏时恢复 卸载有问题的内核…

stm32——hal库学习笔记(DMA实验)

一、DMA介绍(了解) 二、DMA结构框图介绍(熟悉) 三、DMA相关寄存器介绍(熟悉) 四、DMA相关HAL库驱动介绍(掌握) 五、DMA配置步骤(掌握) 六、编程实战&#xff…

训练yolov8+SAM的过程记录

1-首先将拿到的数据集进行重新命名(dataset1:是经过校色之后裁剪的图片;dataset2:原图) 图片文件从1.jpg开始命名的代码: folder_path rC:\Users\23608\Desktop\Luli_work\data\fanStudent\tongueseg\Fan…

【C语言】长篇详解,字符系列篇2-----strcat,strcmp,strncpy,strncat,strncmp函数的使用和模拟实现【图文详解】

欢迎来CILMY23的博客喔,本期系列为【C语言】长篇详解,字符系列篇2-----“混杂”的字符串函数,字符串函数的使用和模拟实现【图文详解】,图文讲解各种字符串函数,带大家更深刻理解C语言中各种字符串函数的应用&#xff…

springmvc+ssm+springboot房屋中介服务平台的设计与实现 i174z

本论文拟采用计算机技术设计并开发的房屋中介服务平台,主要是为用户提供服务。使得用户可以在系统上查看房屋出租、房屋出售、房屋求购、房屋求租,管理员对信息进行统一管理,与此同时可以筛选出符合的信息,给笔者提供更符合实际的…

《Linux C编程实战》笔记:消息队列

消息队列是一个存放在内核中的消息链表,每个消息队列由消息队列标识符标识。与管道不同的是消息队列存放在内核中,只有在内核重启(即操作系统重启)或显示地删除一个消息队列时,该消息队列才会被真正的删除。 操作消息…

英伟达推出免训练,可生成连贯图片的文生图模型

目前,多数文生图模型皆使用的是随机采样模式,使得每次生成的图像效果皆不同,在生成连贯的图像方面非常差。 例如,想通过AI生成一套图像连环画,即便使用同类的提示词也很难实现。虽然DALLE 3和Midjourney可以对图像实现…

如何修改unity的背景颜色

要在Unity中将背景颜色设为黑色,可以按照以下步骤进行: 1、在Unity编辑器中,选择你想要修改背景颜色的摄像机对象(一般是Main Camera)。 2、在Inspector面板中,找到"Clear Flags"(清…

掌握Redis核心:常用数据类型的高效运用秘籍!

在数据驱动的时代,高效地存储和处理数据成为了开发者们的重要任务。Redis,作为一个开源的高性能键值对(key-value)数据库,以其独特的数据结构和丰富的功能,成为了众多项目的首选。 今天,我们就…

高精度双向计量旋翼干式脉冲水表

在现代社会,水资源的合理利用与精确管理至关重要。随着科技的不断进步,水表技术也在迭代升级。今天,我们将探讨一种高科技水表——高精度双向计量旋翼干式脉冲水表,它如何改变我们的用水习惯,提升水资源管理的效率。 1…

Stable Diffusion 模型分享:FenrisXL(芬里斯XL)

本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 FenrisXL 是一个拟人化的 SDXL 模型,可以为动物们穿上人类的衣服&…

吴恩达机器学习全课程笔记第三篇

目录 前言 P42-P48 神经元和大脑 神经网络中的层 更复杂的神经网络 前向传播(做出预测) P49-P53 代码中的推理 构建一个神经网络 P54-P60 矩阵乘法 TensorFlow框架实现神经网络 前言 这是吴恩达机器学习笔记的第三篇,第二篇笔记…

苍穹外卖学习-----2024/02/21

1.新增员工 /*** 处理SQL异常* param sqlIntegrityConstraintViolationException* return*/ExceptionHandlerpublic Result exceptionHandler(SQLIntegrityConstraintViolationException sqlIntegrityConstraintViolationException){//String message sqlIntegrityConstraintV…

IDEA启动Springboot报错:无效的目标发行版:17 的解决办法

无效的目标发行版:17 的解决办法 一般有两个原因,一可能是本地没有安装JDK17,需要安装后然后在IDEA中选择对应版本;二可能是因为IDEA版本太低,不支持17,需要升级IDEA版本。然后在File->Project Struct…

Zabbix 6.2.1 安装

目录 1、监控介绍 监控的重要性 网站的可用性 监控范畴 如何监控 2、Zabbix 介绍 zabbix 简介 zabbix 主要功能 zabbix 监控范畴 Zabbix 监控组件 zabbix 常见进程 zabbix agentd 工作模式 zabbix 环境监控中概念 3、搭建LNMP 拓扑规划 安装MySQL 安装 Nginx …

libpng编译-android端(libpng官网下载是没有android编译脚本)

libpng编译-android端(libpng官网下载是没有android编译脚本) 环境配置(mac的原理一样可以自己配置,我在linux编译) 配置ubuntu的ndk环境 1、执行 sudo vim /etc/profile,对Path的路径文件进行更改&…