• 面试时说Redis是单线程的,被喷惨了!


    Redis是单线程的,这话搁以前,是横着走的,谁都知道的真理。现在不一样,Redis 变了。再说这句话,多少得有质疑的语气来跟你辩驳一番。意志不坚定的,可能就缴械投降,顺着别人走了。

    到底是什么样的,各位看官请跟小莱一起往下:

    图注:思维导图

    Reactor模式 

    反应器模式,你可能不太认识,如果看过上篇文章的话应该会有点印象。涉及到 Redis 线程它是一个绕不过去的话题。

    1、传统阻塞IO模型 

    在讲反应器模式前,这里有必要提一下传统阻塞IO模型的处理方式。

    在传统阻塞IO模型中,由一个独立的 Acceptor 线程来监听客户端的连接,每当有客户端请求过来时,它就会为客户端分配一个新的线程来进行处理。当同时有多个请求过来,服务端对应的就会分配相应数量的线程。这就会导致CPU频繁切换,浪费资源。

    有的连接请求过来不做任何事情,但服务端还会分配对应的线程,这样就会造成不必要的线程开销。这就好比你去餐厅吃饭,你拿着菜单看了半天发现真他娘的贵,然后你就走人了。这段时间等你点菜的服务员就相当于一个对应的线程,你要点菜可以看作一个连接请求。

     

    同时,每次建立连接后,当线程调用读写方法时,线程会被阻塞,直到有数据可读可写, 在此期间线程不能做其它事情。还是上边餐厅吃饭的例子,你出去转了一圈发现还是这家性价比最高。回到这家餐厅又拿着菜单看了半天,服务员也在旁边等你点完菜为止。这个过程中服务员什么也不能做,只能这么干等着,这个过程相当于阻塞。

     

    你看这样的方式,每来一个请求就要分配一个线程,并且还得阻塞地等线程处理完。有的请求还只是过来连接下,什么操作也不干,还得为它分配一个线程,对服务器资源要求那得多高啊。遇到高并发场景,不敢想象。对于连接数目比较小的的固定架构倒是可以考虑。

    2、伪异步IO模型

    你可能了解过一种通过线程池优化的解决方案,采用线程池和任务队列的方式。这种被称作伪异步IO模型。

    当有客户端接入时,将客户端的请求封装成一个 task 投递到后端线程池中来处理。线程池维护一个消息队列和多个活跃线程,对消息队列中的任务进行处理。

    这种解决方案,避免了为每个请求创建一个线程导致的线程资源耗尽问题。但是底层仍然是同步阻塞模型。如果线程池内的所有线程都阻塞了,那么对于更多请求就无法响应了。因此这种模式会限制最大连接数,并不能从根本上解决问题。

    我们继续用上边的餐厅来举例,餐厅老板在经营了一段时间后,顾客多了起来,原本店里的5个服务员一对一服务的话根本对付不过来。于是老板采用5个人线程池的方式。服务员服务完一个客人后立刻去服务另一个。

    这时问题出现了,有的客人点菜特别慢,服务员就得等待很长时间,直到客人点完为止。如果5个客人都点的特别慢的话,这5个服务员就得一直等下去,就会导致其余的顾客没有人服务的状态。这就是我们上边所说的线程池所有线程都被阻塞的情况。

    那么这种问题该如何解决呢?别急, Reactor 模式就要出场了。

    3、Reactor设计模式

    Reactor 模式的基本设计思想是基于I/O复用模型来实现的。

     这里说下I/O复用模型。和传统IO多线程阻塞不同,I/O复用模型中多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象等待。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。

    什么意思呢?餐厅老板也发现了顾客点餐慢的问题,于是他采用了一种大胆的方式,只留了一个服务员。当客人点餐的时候,这个服务员就去招待别的客人,客人点好餐后直接喊服务员来进行服务。这里的顾客和服务员可以分别看作多个连接和一个线程。服务员阻塞在一个顾客那里,当有别的顾客点好餐后,她就立刻去服务其他的顾客。

    了解了 reactor 的设计思想后,我们再来看下今天的主角单 reactor 单线程的实现方案:

    Reactor 通过 I/O复用程序监控客户端请求事件,收到事件后通过任务分派器进行分发。

    针对建立连接请求事件,通过 Acceptor 处理,并建立对应的 handler 负责后续业务处理。

    针对非连接事件,Reactor 会调用对应的 handler 完成 read->业务处理->write 处理流程,并将结果返回给客户端。

    整个过程都在一个线程里完成。

     

    单线程时代

    了解了 Reactor 模式后,你可能会有一个疑问,这个和我们今天的主题有什么关系呢。可能你不知道的是,Redis 是基于 Reactor 单线程模式来实现的。

    IO多路复用程序接收到用户的请求后,全部推送到一个队列里,交给文件分派器。对于后续的操作,和在 reactor 单线程实现方案里看到的一样,整个过程都在一个线程里完成,因此 Redis 被称为是单线程的操作。

    对于单线程的 Redis 来说,基于内存,且命令操作时间复杂度低,因此读写速率是非常快的。

    多线程时代

    Redis6 版本中引入了多线程。上边已经提到过 Redis 单线程处理有着很快的速度,那为什么还要引入多线程呢?单线程的瓶颈在什么地方?

    我们先来看第二个问题,在 Redis 中,单线程的性能瓶颈主要在网络IO操作上。也就是在读写网络 read/write 系统调用执行期间会占用大部分 CPU 时间。如果你要对一些大的键值对进行删除操作的话,在短时间内是删不完的,那么对于单线程来说就会阻塞后边的操作。

    回想下上边讲得 Reactor 模式中单线程的处理方式。针对非连接事件,Reactor 会调用对应的 handler 完成 read->业务处理->write 处理流程,也就是说这一步会造成性能上的瓶颈。

    Redis 在设计上采用将网络数据读写和协议解析通过多线程的方式来处理,对于命令执行来说,仍然使用单线程操作。

    总结

    Reactor模式

    • 传统阻塞IO模型客户端与服务端线程1:1分配,不利于进行扩展。

    • 伪异步IO模型采用线程池方式,但是底层仍然使用同步阻塞方式,限制了最大连接数。

    • Reactor 通过 I/O复用程序监控客户端请求事件,通过任务分派器进行分发。

    单线程时代

    • 基于 Reactor 单线程模式实现,通过IO多路复用程序接收到用户的请求后,全部推送到一个队列里,交给文件分派器进行处理。

    多线程时代

    • 单线程性能瓶颈主要在网络IO上。

    • 将网络数据读写和协议解析通过多线程的方式来处理 ,对于命令执行来说,仍然使用单线程操作。

    关于作者

    作者:大家好,我是莱乌,BAT搬砖工一枚。从小公司进入大厂,一路走来收获良多,想将这些经验分享给有需要的人,因此创建了公众号「IT界农民工」。定时更新,希望能帮助到你

  • 相关阅读:
    欧拉项目第十题;用筛法做,
    欧拉第十题 求2000000以内的素数之和
    求1000数字中13个相乘最大值
    筛法求10000以内的质数
    判断回文且寻找三位数乘三位数里最大的回文
    分解质因数
    输入一个正整数n,将其转换为二进制后输出。要求定义并调用函数dectobin(n),它的功能是输出
    hdu1863最小生成树krus模板&prim模板
    hdu 3870 最小割转化为最短路径2
    UVALive 3661 最小割转化为最短路径
  • 原文地址:https://www.cnblogs.com/caoyier/p/13913922.html
Copyright © 2020-2023  润新知