网站建设和运营的课程,鞍山吧台,dw做网站字体做多大,做直播网站需要学什么【Redis】线程模型#xff1a;Redis是单线程还是多线程#xff1f; 文章目录【Redis】线程模型#xff1a;Redis是单线程还是多线程#xff1f;Redis 是单线程吗#xff1f;Redis 单线程模式是怎样的#xff1f;Redis 采用单线程为什么还这么快#xff1f;Redis 6.0 之前…【Redis】线程模型Redis是单线程还是多线程 文章目录【Redis】线程模型Redis是单线程还是多线程Redis 是单线程吗Redis 单线程模式是怎样的Redis 采用单线程为什么还这么快Redis 6.0 之前为什么使用单线程Redis 6.0 之后为什么引入了多线程参考资料Redis 线程模型 Redis不是一直号称单线程效率也很高吗为什么又采用多线程了 【Redis】网络模型Redis的IO多路复用 为什么单线程的 Redis 如何做到每秒数万 QPS Redis 是单线程吗
Redis 单线程指的是「接收客户端请求-解析请求 -进行数据读写等操作-发送数据给客户端」这个过程是由一个线程主线程来完成的这也是我们常说 Redis 是单线程的原因。
但是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不停轮询这个队列拿出任务就去执行对应的方法即可。 关闭文件、AOF 刷盘、释放内存这三个任务都有各自的任务队列
BIO_CLOSE_FILE关闭文件任务队列当队列有任务后后台线程会调用 close(fd) 将文件关闭BIO_AOF_FSYNCAOF刷盘任务队列当 AOF 日志配置成 everysec 选项后主线程会把 AOF 写日志操作封装成一个任务也放到队列中。当发现队列有任务后后台线程会调用 fsync(fd)将 AOF 文件刷盘BIO_LAZY_FREElazy 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 函数等待事件的到来 如果是连接事件到来则会调用连接事件处理函数该函数会做这些事情调用 accpet 获取已连接的 socket - 调用 epoll_ctl 将已连接的 socket 加入到 epoll - 注册「读事件」处理函数如果是读事件到来则会调用读事件处理函数该函数会做这些事情调用 read 获取客户端发送的数据 - 解析命令 - 处理命令 - 将客户端对象添加到发送队列 - 将执行结果写到发送缓存区等待发送如果是写事件到来则会调用写事件处理函数该函数会做这些事情通过 write 函数将客户端发送缓存区里的数据发送出去如果这一轮数据没有发送完就会继续注册写事件处理函数等待 epoll_wait 发现可写后再处理 。
以上就是 Redis 单线模式的工作方式。
关于epoll等Redis的多路IO复用可以参考【Redis】网络模型Redis的IO多路复用
如果你想看源码解析可以参考为什么单线程的 Redis 如何做到每秒数万 QPS
Redis 采用单线程为什么还这么快
官方使用基准测试的结果是单线程的 Redis 吞吐量可以达到 10W/每秒如下图所示 之所以 Redis 采用单线程网络 I/O 和执行命令那么快有如下几个原因
Redis 的大部分操作都在内存中完成并且采用了高效的数据结构因此 Redis 瓶颈可能是机器的内存或者网络带宽而并非 CPU既然 CPU 不是瓶颈那么自然就采用单线程的解决方案了Redis 采用单线程模型可以避免了多线程之间的竞争省去了多线程切换带来的时间和性能上的开销而且也不会导致死锁问题。Redis 采用了 I/O 多路复用机制处理大量的客户端 Socket 请求IO 多路复用机制是指一个线程处理多个 IO 流就是我们经常听到的 select/epoll 机制。简单来说在 Redis 只运行单线程的情况下该机制允许内核中同时存在多个监听 Socket 和已连接 Socket。内核会一直监听这些 Socket 上的连接请求或数据请求。一旦有请求到达就会交给 Redis 线程处理这就实现了一个 Redis 线程处理多个 IO 流的效果。
Redis 6.0 之前为什么使用单线程
我们都知道单线程的程序是无法利用服务器的多核 CPU 的那么早期 Redis 版本的主要工作网络 I/O 和执行命令为什么还要使用单线程呢我们不妨先看一下Redis官方给出的FAQ 核心意思是CPU 并不是制约 Redis 性能表现的瓶颈所在更多情况下是受到内存大小和网络I/O的限制所以 Redis 核心网络模型使用单线程并没有什么问题如果你想要使用服务的多核CPU可以在一台服务器上启动多个节点或者采用分片集群的方式。
除了上面的官方回答选择单线程的原因也有下面的考虑。
使用了单线程后可维护性高多线程模型虽然在某些方面表现优异但是它却引入了程序执行顺序的不确定性带来了并发读写的一系列问题增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。
Redis 6.0 之后为什么引入了多线程 多线程适用场景 一个计算机程序在执行的过程中主要需要进行两种操作分别是读写操作和计算操作。其中读写操作主要是涉及到的就是I/O操作其中包括网络I/O和磁盘I/O。计算操作主要涉及到CPU。 而多线程的目的就是通过并发的方式来提升I/O的利用率和CPU的利用率。 那么Redis需不需要通过多线程的方式来提升提升I/O的利用率和CPU的利用率呢 首先我们可以肯定的说Redis不需要提升CPU利用率因为**Redis的操作基本都是基于内存的CPU资源根本就不是Redis的性能瓶颈。**所以通过多线程技术来提升Redis的CPU利用率这一点是完全没必要的。 那么使用多线程技术来提升Redis的I/O利用率呢是不是有必要呢 Redis确实是一个I/O操作密集的框架他的数据操作过程中会有大量的网络I/O和磁盘I/O的发生。要想提升Redis的性能是一定要提升Redis的I/O利用率的这一点毋庸置疑。 但是提升I/O利用率并不是只有采用多线程技术这一条路可以走 多线程的弊端 我们在很多文章中介绍过一些Java中的多线程技术如内存模型、锁、CAS等这些都是Java中提供的一些在多线程情况下保证线程安全的技术。 和Java类似所有支持多线程的编程语言或者框架都不得不面对的一个问题那就是如何解决多线程编程模式带来的共享资源的并发控制问题。 虽然采用多线程可以帮助我们提升CPU和I/O的利用率但是多线程带来的并发问题也给这些语言和框架带来了更多的复杂性。而且多线程模型中多个线程的互相切换也会带来一定的性能开销。 所以在提升I/O利用率这个方面上Redis并没有采用多线程技术而是选择了多路复用 I/O技术。 虽然 Redis 的主要工作网络 I/O 和执行命令一直是单线程模型但是在 Redis 6.0 版本之后也采用了多个 I/O 线程来处理网络请求这是因为随着网络硬件的性能提升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 多线程只针对发送响应数据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表示启用 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 所以会启动 34-1个 I/O 多线程用来分担 Redis 网络 I/O 的压力。 y_free三个后台线程分别异步处理关闭文件任务、AOF刷盘任务、释放内存任务io_thd_1、io_thd_2、io_thd_3三个 I/O 线程io-threads 默认是 4 所以会启动 34-1个 I/O 多线程用来分担 Redis 网络 I/O 的压力。