Redis 线程模式

news/2024/10/23 7:36:04/

Redis 是单线程吗?

Redis 单线程指的是 [接收客户端请求 -> 解析请求 -> 进行数据读写操作 -> 发送数据给客户端] 这个过程是由一个线程 (主线程) 来完成的,这也是常说的 Redis 是单线程的原因。

但是 ,Redis 程序不是单线程的,Redis 在启动的时候,是会 启动后台线程 的:

  • 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)不停轮询这个队列,拿出任务就去执行对应的方法即可。

关闭文件、AOF刷盘、释放内存这三个任务都有各自的任务队列:

  • BIO_CLOSE_FILE,关闭文件任务队列:当队列有任务后,后台线程会调用 close(fd),将文件关闭;
  • BIO_AOF_FSYNC,AOF刷盘任务队列:当 AOF日志配置成 everysec 选项后,主线程会把 AOF写日志操作封装成一个任务,也放到队列中。当发现队列有任务后,后台线程会调用 fsync(fd) ,将 AOF 文件刷盘;
  • BIO_LAZY_FREE,lazy free 任务队列:当队列有任务后,后台线程会 free(obj)释放对象 / free(dict) 删除数据库所有对象 / free(skiplist) 释放跳表对象;

Redis 单线程模式是怎样的?

Redis 6.0 版本之前的单线程模式如下图:

图中的蓝色部分是一个事件循环,是由主线程负责的,可以看到网络 I/O 和命令处理都是单线程。Redis 初始化的时候,会做下面几件事情:

  • 首先,调用epoll_create() 创建一个 epoll 对象和调用 socket() 创建一个服务端 socket
  • 然后,调用 bind() 绑定端口和调用 listen() 监听该socket
  • 然后,将调用epoll_ctl()将 listen socket 加入到 epoll ,同时注册 [连接事件] 处理函数。

初始化完成后,主线程就进入到一个事件循环函数,主要会做以下事情:

  • 首先,先调用 处理发送队列函数,看发送队列里是否有任务,如果有发送任务,则通过 write 函数将客户端发送缓存区里的数据发送出去,如果这一轮数据没有发送完,就会注册写事件处理函数,等待 epoll_wait 发现可写后再处理
  • 接着,调用 epoll_wait 函数等待事件的到来:
    • 如果是连接事件到来,则会调用 连接事件处理函数,该函数会做这些事情:调用 accept 获取已连接的 socket -> 调用 epoll_ctl 将已连接的 socket 加入到 epoll -> 注册 [读事件] 处理函数;
    • 如果是读事件到来,则会调用 读事件处理函数,该函数会做这些事情:调用 read 获取客户端发送的数据 -> 解析命令 -> 处理命令 -> 将客户端对象添加到发送队列 -> 将执行结果写到发送缓冲区等待发送;
    • 如果是写事件到来,则会调用 写事件处理函数,该函数会做这些事情:通过 write 函数将客户端发送缓冲区里的数据发送出去,如果这一轮数据没有发送完,就会继续注册写事件处理函数,等待 epoll_wait 发现可写后再处理。

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

主要有以下几个原因:

  • Redis 的大部分操作都在内存中完成,并且采用了高效的数据结构,因此 Redis 瓶颈可能是机器的内存或者网络带宽,而并非 CPU ,既然 CPU不是瓶颈,那么自然就采用单线程的解决方案了。
  • Redis 采用但线程模型可以避免多线程之间的竞争,省去了多线程切换带来的时间和性能上的开销,而且页不会导致死锁的问题
  • Redis 采用了 I / O 多路复用机制 处理大量的客户端Socket 请求,IO多路复用机制是指一个线程处理多个 IO 流。就是我们经常听到的 select/epoll 机制。简单来首,在Redis只运行单线程的情况下,该机制允许内核中,同时存在多个监听 Socket 和已连接 Socket 。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达,就会交付给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO流的效果。

为什么 6.0 之前使用的是单线程?

核心意思是:CPU并不是制约 Redis 性能表现的瓶颈所在,更多情况下是收到内存大小和网络I/O的限制,所以 Redis 核心网络模型使用单线程并没有什么问题,如果想要使用服务的多核CPU ,可以在一台服务器上启动多个节点或者采用分片式集群的方式。

除此之外:

使用单线程后,可维护性高,多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗


Redis 6.0 后为什么引入了多线程?

这是因为随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络的 I / O 的处理上

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

Redis官方表示Redis 6.0 版本引入的多线程 I/O 特性对性能提升至少是一倍以上。

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

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

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

// io-threads N,表示启用 N-1 个 I/O 多线程(主线程也算一个 I/O 线程)
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.ppmy.cn/news/1126702.html

相关文章

初级软件测试入门教程

一、软件测试的基本概念 1、软件测试的定义 就是以发现错误为目的而运行程序的过程。 软件测试员的目标是找到软件缺陷,尽可能早一些,并确保其得以修复。 2、软件测试方法总体分类 试图验证软件是“工作的”(所谓“工作的”就是指软件的…

深度学习-学习率调度,正则化,dropout

正如前面我所说的,各种优化函数也依赖于学习率,保持学习率恒定总是有所限制,在执行梯度下降过程中,我们可以使用各种方法来调节训练过程的学习率,这里只是稍微介绍一下,不会写代码实现的。同时,…

pip install open-interpreter报错,无法安装

标题pip install open-interpreter报错,无法安装 ERROR: Could not find a version that satisfies the requirement open-interpreter (from versions: none) ERROR: No matching distribution found for open-interpreter 另外发现自己换了很多国内镜像源&#x…

C++ Primer----1.5类简介 章节练习

头文件 Sales_item.h #ifndef SALESITEM_H #define SALESITEM_H #include <iostream> #include <string>class Sales_item{ public:Sales_item(const std::string &book):isbn(book),units_sold(0),revenue(0.0){}Sales_item(std::istream &is){ is >&…

使用Python做一个微信机器人

介绍 简介 该程序将微信的内部功能提取出来&#xff0c;然后在程序里加载Python&#xff0c;接着将这些功能导出成库函数&#xff0c;就可以在Python里使用这些函数 程序启动的时候会执行py_code目录下的main.py&#xff0c;类似于你在命令行使用python main.py。 现在会以…

CasaOS:一个docker容器应用的可视化Portal

CasaOS 官网声称他是一个家庭云操作系统&#xff0c;但我实际使用后感觉称之为“docker容器的可视化Portal”更合适。因为它本身不具备IAAS、PAAS、或SAAS的开箱即用能力&#xff0c;更像是一个把OS上的docker Container集中管理并展示的索引目录&#xff0c;各个docker Contai…

LetCode算法题

注:大佬解答来自LetCode官方题解 88.合并两个有序数组 1.题目 2.个人解答 var merge function (nums1, m, nums2, n) {//合并for (let i 0; i < n; i) {nums1[i m] nums2[i];}// 排序&#xff08;冒泡排序&#xff09;for (let i 0; i < m n; i) {for (let j 0…

git 查看当前版本号

你看&#xff0c;那个人好像一条狗哎。 ——周星驰 《大话西游》 要查看当前 Git 仓库的版本号&#xff0c;您可以使用以下命令&#xff1a; git log --oneline -n 1 这会显示最近一次的提交信息&#xff0c;包括提交的哈希值&#xff08;版本号&#xff09;和提交的摘要信息…