Redis线程模型的前世今生

前言

众所周知,Redis是一个高性能的数据存储框架,在高并发的系统设计中,Redis也是一个比较关键的组件,是我们提升系统性能的一大利器。深入去理解Redis高性能的原理显得越发重要,当然Redis的高性能设计是一个系统性的工程,涉及到很多内容,本文重点关注Redis的IO模型,以及基于IO模型的线程模型

我们从IO的起源开始,讲述了阻塞IO、非阻塞IO、多路复用IO。基于多路复用IO,我们也梳理了几种不同的Reactor模型,并分析了几种Reactor模型的优缺点。基于Reactor模型我们开始了Redis的IO模型和线程模型的分析,并总结出Redis线程模型的优点、缺点,以及后续的Redis多线程模型方案。本文的重点是对Redis线程模型设计思想的梳理,捋顺了设计思想,就是一通百通的事了。

注:本文的代码都是伪代码,主要是为了示意,不可用于生产环境。

我们常说的网络IO模型,主要包含阻塞IO、非阻塞IO、多路复用IO、信号驱动IO、异步IO,本文重点关注跟Redis相关的内容,所以我们重点分析阻塞IO、非阻塞IO、多路复用IO,帮助大家后续更好的理解Redis网络模型。

我们先看下面这张图;

我们经常说的阻塞IO其实分为两种,一种是单线程阻塞,一种是多线程阻塞。这里面其实有两个概念,阻塞和线程。

阻塞:指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回;

线程:系统调用的线程个数。

像建立连接、读、写都涉及到系统调用,本身是一个阻塞的操作。

服务端单线程来处理,当客户端请求来临时,服务端用主线程来处理连接、读取、写入等操作。

以下用代码模拟了单线程的阻塞模式;

我们准备用两个客户端同时发起连接请求、来模拟单线程阻塞模式的现象。同时发起连接,通过服务端日志,我们发现此时服务端只接受了其中一个连接,主线程被阻塞在上一个连接的read方法上。

我们尝试关闭第一个连接,看第二个连接的情况,我们希望看到的现象是,主线程返回,新的客户端连接被接受。

从日志中发现,在第一个连接被关闭后,第二个连接的请求被处理了,也就是说第二个连接请求在排队,直到主线程被唤醒,才能接收下一个请求,符合我们的预期。

此时不仅要问,为什么呢?

主要原因在于accept、read、write三个函数都是阻塞的,主线程在系统调用的时候,线程是被阻塞的,其他客户端的连接无法被响应。

通过以上流程,我们很容易发现这个过程的缺陷,服务器每次只能处理一个连接请求,CPU没有得到充分利用,性能比较低。如何充分利用CPU的多核特性呢?自然而然的想到了——多线程逻辑。

对工程师而言,代码解释一切,直接上代码。

BIO多线程

同样,我们并行发起两个请求;

两个请求,都被接受,服务端新增两个线程来处理客户端的连接和后续请求。

我们用多线程解决了,服务器同时只能处理一个请求的问题,但同时又带来了一个问题,如果客户端连接比较多时,服务端会创建大量的线程来处理请求,但线程本身是比较耗资源的,创建、上下文切换都比较耗资源,又如何去解决呢?

如果我们把所有的Socket(文件句柄,后续用Socket来代替fd的概念,尽量减少概念,减轻阅读负担)都放到队列里,只用一个线程来轮训所有的Socket的状态,如果准备好了就把它拿出来,是不是就减少了服务端的线程数呢?

一起看下代码,单纯非阻塞模式,我们基本上不用,为了演示逻辑,我们模拟了相关代码如下;

系统初始化,等待连接;

发起两个客户端连接,线程开始轮询两个连接中是否有数据。

两个连接分别输入数据后,轮询线程发现有数据准备好了,开始相关的逻辑处理(单线程、多线程都可)。

再用一张流程图辅助解释下(系统实际采用文件句柄,此时用Socket来代替,方便大家理解)。

服务端专门有一个线程来负责轮询所有的Socket,来确认操作系统是否完成了相关事件,如果有则返回处理,如果无继续轮询,大家一起来思考下?此时又带来了什么问题呢。

CPU的空转、系统调用(每次轮询到涉及到一次系统调用,通过内核命令来确认数据是否准备好),造成资源的浪费,那有没有一种机制,来解决这个问题呢?

server端有没专门的线程来做轮询操作(应用程序端非内核),而是由事件来触发,当有相关读、写、连接事件到来时,主动唤起服务端线程来进行相关逻辑处理。模拟了相关代码如下;

IO多路复用

同时创建两个连接;

两个连接无阻塞的被创建;

无阻塞的接收读写;

再用一张流程图辅助解释下(系统实际采用文件句柄,此时用Socket来代替,方便大家理解)。

当然操作系统的多路复用有好几种实现方式,我们经常使用的select(),epoll模式这里不做过多的解释,有兴趣的可以查看相关文档,IO的发展后面还有异步、事件等模式,我们在这里不过多的赘述,我们更多的是为了解释Redis线程模式的发展。

我们一起来聊了阻塞、非阻塞、IO多路复用模式,那Redis采用的是哪种呢?

Redis采用的是IO多路复用模式,所以我们重点来了解下多路复用这种模式,如何在更好的落地到我们系统中,不可避免的我们要聊下Reactor模式。

首先我们做下相关的名词解释;

Reactor:类似NIO编程中的Selector,负责I/O事件的派发;

Acceptor:NIO中接收到事件后,处理连接的那个分支逻辑;

Handler:消息读写处理等操作类。

处理流程

优点

模型比较简单,所有的处理过程都在一个连接里;

实现上比较容易,模块功能也比较解耦,Reactor负责多路复用和事件分发处理,Acceptor负责连接事件处理,Handler负责Scoket读写事件处理。

缺点

只有一个线程,连接处理和业务处理共用一个线程,无法充分利用CPU多核的优势。

在流量不是特别大、业务处理比较快的时候系统可以有很好的表现,当流量比较大、读写事件比较耗时情况下,容易导致系统出现性能瓶颈。

怎么去解决上述问题呢?既然业务处理逻辑可能会影响系统瓶颈,那我们是不是可以把业务处理逻辑单拎出来,交给线程池来处理,一方面减小对主线程的影响,另一方面利用CPU多核的优势。这一点希望大家要理解透彻,方便我们后续理解Redis由单线程模型到多线程模型的设计的思路。

这种模型相对单Reactor单线程模型,只是将业务逻辑的处理逻辑交给了一个线程池来处理。

处理流程

Reactor监听连接事件、Socket事件,当有连接事件过来时交给Acceptor处理,当有Socket事件过来时交个对应的Handler处理。

Handler完成读事件后,包装成一个任务对象,交给线程池来处理,把业务处理逻辑交给其他线程来处理。

优点

让主线程专注于通用事件的处理(连接、读、写),从设计上进一步解耦;

利用CPU多核的优势。

缺点

有没有什么好的办法来解决上述问题呢?通过以上的分析,大家有没有发现一个现象,当某一个点成为系统瓶颈点时,想办法把他拿出来,交个其他线程来处理,那这种场景是否适用呢?

这种模型相对单Reactor多线程模型,只是将Scoket的读写处理从mainReactor中拎出来,交给subReactor线程来处理。

处理流程

mainReactor主线程负责连接事件的监听和处理,当Acceptor处理完连接过程后,主线程将连接分配给subReactor;

subReactor负责mainReactor分配过来的Socket的监听和处理,当有Socket事件过来时交个对应的Handler处理;

Handler完成读事件后,包装成一个任务对象,交给线程池来处理,把业务处理逻辑交给其他线程来处理。

优点

让主线程专注于连接事件的处理,子线程专注于读写事件吹,从设计上进一步解耦;

利用CPU多核的优势。

缺点

以上我们聊了,IO网路模型的发展历史,也聊了IO多路复用的reactor模式。那Redis采用的是哪种reactor模式呢?在回答这个问题前,我们先梳理几个概念性的问题。

Redis服务器中有两类事件,文件事件和时间事件。

文件事件:在这里可以把文件理解为Socket相关的事件,比如连接、读、写等;

时间时间:可以理解为定时任务事件,比如一些定期的RDB持久化操作。

本文重点聊下Socket相关的事件。

首先我们来看下Redis服务的线程模型图;

IO多路复用负责各事件的监听(连接、读、写等),当有事件发生时,将对应事件放入队列中,由事件分发器根据事件类型来进行分发;

如果是连接事件,则分发至连接应答处理器;GET、SET等redis命令分发至命令请求处理器。

命令处理完后产生命令回复事件,再由事件队列,到事件分发器,到命令回复处理器,回复客户端响应。

连接过程

Redis服务端主线程监听固定端口,并将连接事件绑定连接应答处理器。

客户端发起连接后,连接事件被触发,IO多路复用程序将连接事件包装好后丢人事件队列,然后由事件分发处理器分发给连接应答处理器。

连接应答处理器创建client对象以及Socket对象,我们这里关注Socket对象,并产生ae_readable事件,和命令处理器关联,标识后续该Socket对可读事件感兴趣,也就是开始接收客户端的命令操作。

当前过程都是由一个主线程负责处理。

SET命令执行过程

客户端发起SET命令,IO多路复用程序监听到该事件后(读事件),将数据包装成事件丢到事件队列中(事件在上个流程中绑定了命令请求处理器);

事件分发处理器根据事件类型,将事件分发给对应的命令请求处理器;

命令请求处理器,读取Socket中的数据,执行命令,然后产生ae_writable事件,并绑定命令回复处理器;

IO多路复用程序监听到写事件后,将数据包装成事件丢到事件队列中,事件分发处理器根据事件类型分发至命令回复处理器;

命令回复处理器,将数据写入Socket中返回给客户端。

以上流程分析我们可以看出Redis采用的是单线程Reactor模型,我们也分析了这种模式的优缺点,那Redis为什么还要采用这种模式呢?

Redis本身的特性

命令执行基于内存操作,业务处理逻辑比较快,所以命令处理这一块单线程来做也能维持一个很高的性能。

优点

缺点

Reactor单线程模型的缺点也同样在Redis中来体现,唯一不同的地方就在于业务逻辑处理(命令执行)这块不是系统瓶颈点。

随着流量的上涨,IO操作的的耗时会越来越明显(read操作,内核中读数据到应用程序。write操作,应用程序中的数据到内核),当达到一定阀值时系统的瓶颈就体现出来了。

Redis又是如何去解的呢?

哈哈~将耗时的点从主线程拎出来呗?那Redis的新版本是这么做的吗?我们一起来看下。

Redis的多线程模型跟”多Reactor多线程模型“、“单Reactor多线程模型有点区别”,但同时用了两种Reactor模型的思想,具体如下;

Redis的多线程模型是将IO操作多线程化,本身逻辑处理过程(命令执行过程)依旧是单线程,借助了单Reactor思想,实现上又有所区分。

将IO操作多线程化,又跟单Reactor衍生出多Reactor的思想一致,都是将IO操作从主线程中拎出来。

命令执行大致流程

客户端发送请求命令,触发读就绪事件,服务端主线程将Socket(为了简化理解成本,统一用Socket来代表连接)放入一个队列,主线程不负责读;

IO 线程通过Socket读取客户端的请求命令,主线程忙轮询,等待所有 I/O 线程完成读取任务,IO线程只负责读不负责执行命令;

主线程一次性执行所有命令,执行过程和单线程一样,然后需要返回的连接放入另外一个队列中,有IO线程来负责写出(主线程也会写);

主线程忙轮询,等待所有 I/O 线程完成写出任务。

了解一个组件,更多的是要去了解他的设计思路,要去思考为什么要这么设计,做这种技术选型的背景是啥,对后续做系统架构设计有什么参考意义等等。一通百通,希望对大家有参考意义。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容