一文看懂Redis 6.0多线程IO
本篇博客的视频教程首发于 Youtube:科技小飞哥,加入 电报粉丝群 获得最新视频更新和问题解答。
Redis基础
Redis是什么
Redis是一个基于BSD开源的项目,是一个把结构化的数据放在内存中的一个存储系统。
你可以把它作为数据库,缓存和消息中间件来使用。同时支持strings
,lists
,hashes
,sets
,sorted sets
,bitmaps
,hyperloglogs
和geospatial indexes
等数据类型。
它还通过redis sentinel实现高可用,通过redis cluster实现了自动分片。以及事务,发布/订阅,自动故障转移等等。
为什么用Redis
而在后端开发的技术选型中,Redis已经成为了一个不可绕过的解决方案工具。因此Redis成为了后端开发的基本技能之一。当然,也是后端面试中必考的技术栈之一。
Redis的优点,如果只用一个字来解释,那就是:快!
Redis 有多快?官方给出的答案是读写速度 10万/秒,如果说这是在单线程情况下跑出来的成绩,你会不会惊讶?为什么单线程的 Redis 速度这么快?
Redis为什么快
主要有以下几点:
- Redis 是基于内存的。 内存的读写速度非常快。当然Redis也存在持久化操作,但是是fork子进程和利用 Linux 系统的页缓存技术来完成,并不会影响Redis的读写性能。
- Redis 是单线程的。 避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
- Redis 使用多路复用技术。 可以处理并发的连接。非阻塞 IO 部实现采用 epoll,采用了 epoll+自己实现的简单的事件框架。epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,绝不在 IO 上浪费一点时间。
- Redis 中的数据结构是专门进行设计的。 数据结构简单。对数据操作也简单。
Redis是单线程的吗
我们经常听到,Redis是单线程的,这句话对吗?
基本上是对的,但是不准确。
而对于为什么使用单线程,官方有一句解释:
It’s not very frequent that CPU becomes your bottleneck with Redis, as usually Redis is either memory or network bound.
意思就是:
因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈。Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。
为什么说不准确呢?
我们需要回顾Redis的两个最重要的版本更新:
-
Redis 4.0
为了防止耗时的命令阻塞线程,导致无法处理后续事件。引入了多线程来处理一些非阻塞命令。有:UNLINK
、FLUSHALL ASYNC
、FLUSHDB ASYNC
等。
但是整个网络模型依然是单线程的,所以我们称之为单线程。 -
Redis 6.0
就真正的在网络模型上加入多线程IO
来解决网络IO的性能瓶颈。 此时IO读写是多线程的,执行命令依旧是单线程的。
Redis网络模型
一张图看懂Redis的单线程模型:
redis的网络事件处理器是基于Reactor模式,又叫做文件事件处理器。
文件事件处理器
使用I/O多路复用
来同时监听多个套接字,并根据套接字执行的任务关联到不同的事件处理器。
文件事件以单线程方式运行,但通过使用I/O多路复用
程序来监听多个套接字,文件事件处理器
实现了高性能的网络通信模型。
Redis 在处理客户端的请求时,包括接收
(socket读)、解析
、执行
、发送
(socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的单线程
。
Reactor模型
Redis的单线程网络模型,这就是一个经典的Reactor的模型,其本质上是 I/O 多路复用(I/O multiplexing) + 非阻塞 I/O(non-blocking I/O)
的模式。
是一种基于事件驱动模型的设计模式。
我们来看一下Reactor里面两种经典的模型。
单线程Reactor模型
Redis的单线程模型就是使用的经典的单线程Reactor模型。
我们先看看单线程的Reactor模型
消息处理流程:
- Reactor对象通过
select/poll/epoll
等IO多路复用监控连接事件,收到事件后通过dispatcher
事件分发器进行转发。 - 如果是连接建立的事件,则由
acceptor
接受连接,并创建Handler
处理后续事件。 - 如果不是建立连接事件,则Reactor会分发调用
Handler
来响应。 - Handler会完成
read->解析->执行->send
的完整业务流程。
优点:
- 单线程运行,串行操作,不需要加锁,逻辑简单。
缺点:
- 仅用一个线程处理请求,对于多核资源机器来说是有点浪费的。
- 当处理读写任务的线程负载比较重,将会阻塞后续的事件处理,导致整体延迟变大。
应用:
- Redis网络模型。(6.0版本以前)
Master-Worker Reactor模型
比起单线程模型,它是将Reactor分成两部分:
mainReactor
负责监听server socket,用来处理网络IO连接建立操作,将建立的socketChannel指定注册给subReactor。 (只负责监听)subReactor
主要做和建立起来的socket做数据交互和事件业务处理操作。通常,subReactor个数上可与CPU个数等同。一般是多个,这样的话,就可以充分利用多核的优势。 (负责IO读写和命令的执行)
区别于单线程Reactor模式
,这种模式不再是单线程的事件循环,而是有多个线程subReactors
各自维护一个独立的事件循环,由 mainReactor
负责接收新连接并分发给 subReactors
去独立处理,最后 subReactors
回写响应给客户端。
优点:
- 响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
- 可扩展性,可以方便地通过增加Reactor实例个数来充分利用CPU资源;
缺点:
- 如果多个线程可能操作同一份数据,就涉及到底层数据同步的问题,则必然会引入某些同步机制,比如锁。增加了代码复杂度,同时增加了同步机制的开销。
应用:
- Nginx, Netty, Swoole, Memcached就是使用的这个模型
Redis 6.0的多线程网络模型
Redis 6.0版本之后,Redis 正式在核心网络模型中引入了多线程,也就是所谓的 I/O threading
,至此 Redis 真正拥有了多线程模型。
但是Redis的多线程模型却并非标准的Master-Worker Reactor
模型。他的多线程 只负责IO读写,不负责具体的执行。
为什么Redis 6.0 要使用多线程
之前说了,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存大小和网络带宽。
从Redis自身角度来说,因为读写网络的read/write
系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗, 所以选择多线程IO来实现读写。主线程来执行Redis命令。
总结就是:
将主线程 IO 读写任务拆分出来给一组独立的线程处理,使得多个 socket 读写可以并行化,但是 Redis 命令还是主线程串行执行。
Redis 6.0 网络模型
为什么这么设计呢?
- 前面提到 Redis 最初选择单线程网络模型的理由是:CPU 通常不会成为性能瓶颈,瓶颈往往是内存和网络,因此单线程足够了。那么为什么现在 Redis 又要引入多线程呢?很简单,就是 Redis 的
网络 I/O 瓶颈
已经越来越明显了。所以这个多线程是为了解决IO的瓶颈
的。 - 如果多线程包括了
IO读写,解析和执行
的整个过程,那么多线程需要面临线程安全的问题,Redis 6.0版本之前是没有考虑线程安全的,如果使用多线程来处理命令的执行,需要大量的改动来保证多线程的安全机制,实现更复杂。为了避免了不必要的上下文切换和竞争条件,多线程导致的切换而消耗 CPU,也不用考虑各种锁的问题,就让执行这一步只使用主线程。
Redis 6.0和Memcached多线程模型对比
相同点:
- 都采用了 Master-Worker 的线程的模型
不同点:
- Memcached 执行主逻辑也是在 Worker 线程里,模型更加简单,实现了真正的线程隔离,通过各种锁机制来保证数据的线程安全。
- 而 Redis 把执行逻辑交还给 Master 线程,虽然一定程度上增加了模型复杂度,但也解决了数据的线程安全问题。
总结
让我们来回顾一下 Redis 多线程网络模型的设计方案:
- 使用 I/O 线程实现网络 I/O 多线程化,I/O 线程只负责网络 I/O 和命令解析,不执行具体的命令。
Redis 的多线程网络模型实际上并不是一个标准的 Master-Worker Reactor
模型,Redis 的多线程方案中,I/O 线程任务仅仅是通过 socket 读取客户端请求命令并解析,却没有真正去执行命令。
所有客户端命令最后还需要回到主线程去执行
,因此对多核的利用率并不算高,而且每次主线程都必须在分配完任务之后忙轮询等待所有 I/O 线程完成任务之后才能继续执行其他逻辑。
Redis 目前的多线程方案更像是一个折中的选择:既保持了原系统的兼容性,又能利用多核提升 I/O 性能,来解决网络IO的性能瓶颈。
<全文完>