2

一文看懂Redis 6.0多线程IO

 2 years ago
source link: https://www.leftpocket.cn/post/redis/multithreading/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

原文地址:码农在新加坡的个人博客

Redis基础

Redis是什么

Redis是一个基于BSD开源的项目,是一个把结构化的数据放在内存中的一个存储系统。

你可以把它作为数据库,缓存和消息中间件来使用。同时支持stringslistshashessetssorted setsbitmapshyperloglogsgeospatial indexes等数据类型。

它还通过redis sentinel实现高可用,通过redis cluster实现了自动分片。以及事务,发布/订阅,自动故障转移等等。

为什么用Redis

而在后端开发的技术选型中,Redis已经成为了一个不可绕过的解决方案工具。因此Redis成为了后端开发的基本技能之一。当然,也是后端面试中必考的技术栈之一。

Redis的优点,如果只用一个字来解释,那就是:快!

Redis 有多快?官方给出的答案是读写速度 10万/秒,如果说这是在单线程情况下跑出来的成绩,你会不会惊讶?为什么单线程的 Redis 速度这么快?

Redis为什么快

主要有以下几点:

  1. Redis 是基于内存的。 内存的读写速度非常快。当然Redis也存在持久化操作,但是是fork子进程和利用 Linux 系统的页缓存技术来完成,并不会影响Redis的读写性能。
  2. Redis 是单线程的。 避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗。
  3. Redis 使用多路复用技术。 可以处理并发的连接。非阻塞 IO 部实现采用 epoll,采用了 epoll+自己实现的简单的事件框架。epoll 中的读、写、关闭、连接都转化成了事件,然后利用 epoll 的多路复用特性,绝不在 IO 上浪费一点时间。
  4. 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的两个最重要的版本更新:

  1. Redis 4.0 为了防止耗时的命令阻塞线程,导致无法处理后续事件。引入了多线程来处理一些非阻塞命令。有:UNLINKFLUSHALL ASYNCFLUSHDB ASYNC等。
    但是整个网络模型依然是单线程的,所以我们称之为单线程。

  2. Redis 6.0 就真正的在网络模型上加入多线程IO来解决网络IO的性能瓶颈。 此时IO读写是多线程的,执行命令依旧是单线程的。

Redis网络模型

一张图看懂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模型

redis

消息处理流程:

  1. Reactor对象通过select/poll/epoll等IO多路复用监控连接事件,收到事件后通过dispatcher事件分发器进行转发。
  2. 如果是连接建立的事件,则由acceptor接受连接,并创建Handler处理后续事件。
  3. 如果不是建立连接事件,则Reactor会分发调用Handler来响应。
  4. Handler会完成read->解析->执行->send的完整业务流程。
  • 单线程运行,串行操作,不需要加锁,逻辑简单。
  • 仅用一个线程处理请求,对于多核资源机器来说是有点浪费的。
  • 当处理读写任务的线程负载比较重,将会阻塞后续的事件处理,导致整体延迟变大。
  • Redis网络模型。(6.0版本以前)

Master-Worker Reactor模型

redis

比起单线程模型,它是将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

为什么这么设计呢?

  1. 前面提到 Redis 最初选择单线程网络模型的理由是:CPU 通常不会成为性能瓶颈,瓶颈往往是内存和网络,因此单线程足够了。那么为什么现在 Redis 又要引入多线程呢?很简单,就是 Redis 的网络 I/O 瓶颈已经越来越明显了。所以这个多线程是为了解决IO的瓶颈的。
  2. 如果多线程包括了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的性能瓶颈。

<全文完>

欢迎关注我的微信公众号:码农在新加坡,有更多好的技术分享。

pic

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK