二蛋赠书十六期:《高效使用Redis:一书学透数据存储与高可用集群》

news/发布时间2024/9/20 5:41:45

很多人都遇到过这么一道面试题: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 385806PID    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并不是单线程。

01 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,由异步线程执行第二步耗时逻辑。这时候的流程如下图所示:

懒删除流程图

02 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 104648PID    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多线程之后,命令的执行流程如下图所示:

I/O多线程流程图

03 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的执行流程如下图所示:

在这里插入图片描述

BGSAVE执行流程

04 结论

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

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

05赠送书籍

高效使用Redis:一书学透数据存储与高可用集群
推荐语:深入Redis数据结构与底层实现,攻克Redis数据存储与集群管理难题。

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

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

相关文章

day03_登录注销(前端接入登录,异常处理, 图片验证码,获取用户信息接口,退出功能)

文章目录 1. 前端接入登录1.1 修改前端代码1.2 跨域请求1.2.1 跨域请求简介1.2.2 COSR概述CORS简介CORS原理 1.2.3 CORS解决跨域 2. 异常处理2.1 提示空消息分析2.2 系统异常分类2.3 异常处理2.2.1 方案一2.2.2 方案二 3. 图片验证码3.1 图片验证码意义3.2 实现思路3.3 后端接口…

Redis 管道详解

Redis 管道 关键词:Pipeline Pipeline 简介 Redis 是一种基于 C/S 模型以及请求/响应协议的 TCP 服务。通常情况下,一个 Redis 命令的请求、响应遵循以下步骤: 客户端向服务端发送一个查询请求,并监听 Socket 返回&#xff08…

Flutter Dio进阶:使用Flutter Dio拦截器实现高效的API请求管理和身份验证刷新

Flutter笔记 使用Flutter Dio拦截器实现高效的API请求管理和身份验证刷新 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netMy WebSite:http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article…

C++动态链接库;C++链接库链接到C#上

1.在创建项目中选择动态链接库,如果没有可以从右侧搜索,如果还没有重新安装安装包点击修复增加相应功能 2.点击下一步,可以修改项目名称和目录位置点击创建完成项目创建 3.在新建项目内添加新的头文件(.h),…

js 面试 什么是WebSockets?HTTP和HTTPS有什么不同?web worker是什么?

概念: webSocket 是一种在客户端和服务端之间建立持久连接的协议,它提供全双工通信通道,是服务器可以主动向客户端推送数据,同时也可以接受客户端发送的数据。 1 webSocket与https区别? 在网络通信中,We…

vue2.0接入海康威视控件包V3.3.0——海康威视摄像头接入前端页面(webSDK包)模式

第一部分:前置准备工作(安装摄像头插件) 1.首先去海康威视官网-海康开放平台(海康开放平台) 2.如果没有账号的可以先注册一个账号,有账号的直接登录 3.支持与服务-资源中心 4.设备开放 5.设备开放-资源工具 6.选择Web开发包 7.点击Web3.3控件开发包V3.3的立即下载(不用下载开…

Topaz Video AI:一键提升视频品质,智能重塑影像魅力 mac/win版

Topaz Video AI是一款革命性的视频智能处理软件,它利用先进的机器学习和人工智能技术,为视频创作者提供了前所未有的视频增强和修复功能。无论您是专业视频编辑师、摄影师,还是热爱视频创作的爱好者,Topaz Video AI都能帮助您轻松…

网络架构与组网部署——补充

5G网络架构的演进趋势 (1) MEC:多接入边缘计算。首先MEC可以实现5GC的功能,因为5GC是集中在机房中,所以当有MEC后,就可以把MEC下发到基站旁边,这样减少端到端的延时。便于实现5G中不同场景的实…

数电学习笔记——逻辑函数及其描述方法

目录 一、逻辑函数 二、逻辑函数的描述方法 1、逻辑真值表 2、逻辑函数式 3、逻辑图 4、波形图 三、逻辑函数的两种标准形式 1、最小项与最大项 最小项 最小项的性质 最大项 最大项的性质 2、最大项与最小项的关系 3、逻辑函数的最小项之和形式 4、逻辑函数的最…

如何使用Docker部署WBO容器并实现固定公网地址访问本地白板界面

🔥博客主页: 小羊失眠啦. 🎥系列专栏:《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞👍收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默&…

Rust调用同级目录中的rs文件和调用下级目录中的rs文件

一、Rust调用同级目录中的rs文件 Rust新建工程demo02,src文件夹下面新建test.rs文件,这样main.rs文件与它属于同级目录中。 关键点:导入test文件和test文件中的Ellipse模块 mod test;//导入test模块(文件) use test…

css常用的选择器介绍

CSS(层叠样式表)选择器是CSS规则的一部分,它用于选择和定位网页上的元素,以便将样式应用到这些元素上。CSS选择器的种类繁多,每种选择器都有其特定的用途、特点和效率。在这篇文章中,我们将讨论一些常用的C…

牛客前端八股文(每日更新)

1.说说HTML语义化? 得分点:语义化标签、利于页面内容结构化、利于无CSS页面可读、利于SEO、利于代码可读 1,标签语义化是指在开发时尽可能使用有语义的标签,比如header,footer,h,p&#xff0c…

从Spring Boot应用上下文获取Bean定义及理解其来源

前言 在Spring框架中,Bean是组成应用程序的核心单元。特别是在Spring Boot项目中,通过使用SpringApplication.run()方法启动应用后,我们可以获得一个ConfigurableApplicationContext实例,这个实例代表了整个应用程序的运行时环境…

解锁安全之门,Windows Server 2019密码修改攻略大揭秘

1、使用管理员账户登录到 Windows Server 2019 2、打开“控制面板” 3、找到“用户账户”窗口 4、在“用户账户”窗口中,选择“更改你的账户类型” 5、选择要更的账户 6、在更对应的更改账户中,选择“更改密码” 7、在“更改密码”窗口中,输…

矩阵的导数运算(理解分子布局、分母布局)

矩阵的导数运算(理解分子布局、分母布局) 1、分子布局和分母布局 请思考这样一个问题,一个维度为m的向量y对一个标量x的求导,那么结果也是一个m维的向量,那么这个结果向量是行向量,还是列向量呢? 答案是&#xff1a…

在SAP HANA中使用OData(二)

通常有两种方式通过OData来暴露SAP HANA中的数据库对象,一是直接使用Database Object,比如前一篇和本篇文章介绍的例子,这种方式针对于数据已经存在于SAP HANA中,在Repository中没有对应的设计时对象(Design-time Object)&#xf…

aspose转pdf乱码问题

1 问题描述 在centos服务器使用aspose.word转换word文件为pdf的时候显示中文乱码,但是在win服务器上使用可以正常转换。 2 问题原因 通过查资料分析后确认是由于linux服务器缺少对应的字库导致文件转换出现乱码的。 3 解决方案1:环境解决 安装字库,将…

【Redis】安装与部署-Ubuntu

在Ubuntu上部署redis 安装环境:ubuntu 20.04 1.安装Redis: 在终端中运行以下命令来安装Redis: linuxubuntu:/mnt/hgfs/GB28181$ sudo apt install redis-server2.配置Redis: Redis的默认配置文件位于/etc/redis/redis.conf 一…

MATLAB环境下脑电信号EEG的谱分析

脑电信号一直伴随着人类的生命,脑电波是脑神经细胞发生新陈代谢、离子交换时细胞群兴奋突触电位总和,脑电信号的节律性则和丘脑相关,含有丰富的大脑活动信息。通常我们所接触的脑电图都是头皮脑电图,在有些特殊场合还需要皮下部位…
推荐文章