Redis是单线程的,在以前那就是真理,现在再说这句话,估计得有人来跟咱好好聊聊了。索性咱就来看看Redis到底是啥样呢?
⼀、Reactor模式(反应器模式)
1、传统阻塞IO模型
聊反应器模式前,我们先看⼀下传统阻塞IO模型的处理⽅式。
在传统阻塞IO模型中,由⼀个独⽴的Acceptor线程来监听客户端的连接,当有客户端请求过来时,它会为客户端分配⼀个新的线程来进行处理,当同时有多个请求过来,服务端对应的就会分配相应数量的线程,这样一来就会导致CPU频繁进行上下文切换操作,从而导致资源浪费。
有的连接请求过来之后不做任何事情,但服务端还会分配对应的线程,这样就造成不必要的线程开销,好比我们去餐厅吃饭,拿着菜单看了半天发现不合胃口,然后就走了。这段时间等我们点菜的服务员就相当于⼀个对应的线程,我们要点菜这个行为可以看作⼀个连接请求。
同时,每次建⽴连接后,当线程调⽤读写⽅法时,线程会被阻塞,直到有数据可读可写,在此期间线程不能做其它事情。接着上面的例⼦,我们出去转了⼀圈发现还是这家最好吃,回到这家餐厅⼜拿着菜单看了半天,服务员也在旁边等客户点完菜为⽌。这个过程中服务员什么也不能做,只能这么⼲等着,这个过程相当于阻塞。
像上面这样,每来⼀个请求就要分配⼀个线程,并且还得阻塞地等线程处理完。有的请求还只是过来连接下,啥也不干,还得给分配⼀个线程,这得对服务器资源要求那得多高,万一与高并发场景不期而遇了,那彻底疯了,不过对于连接数目比较⼩的的固定架构倒是可以考虑,大家参考下。
2、伪异步IO模型
我们可能接触或者了解过⼀种通过线程池优化的解决方案,意思就是采用线程池和任务队列的方式来处理请求,被称作伪异步IO模型。
过程就是,当有客户端接⼊时,将客户端的请求封装成⼀个task投递到后端线程池中来处理。线程池维护⼀个消息队列和多个活跃线程,对消息队列中的任务进⾏处理。
这种解决方案,避免了为每个请求创建⼀个线程导致的线程资源耗尽问题,但说到底它的底层架构还是基于同步阻塞模型。假如线程池内的所有线程都阻塞了,对于更多请求就无法响应。所以这种模式会限制最⼤连接数,并不能从根本上解决问题。
咱们接着上面的例子来,餐厅⽼板在经营了⼀段时间后,顾客多了,原本店⾥的5个服务员⼀对⼀服务的话根本来不及。于是⽼板采⽤线程池的⽅式,也就是服务员服务完⼀个客⼈后⽴刻去服务另⼀个。
这时就会发现,有的客⼈点菜特别慢,服务员就得等到客⼈点完为⽌。如果5个客⼈都点的特别慢的话,那这5人就得⼀直等下去,导致其余的顾客陷于没有⼈服务的状态,这就是我们上边所说的线程池所有线程都被阻塞的情况。
3、Reactor设计模式
Reactor模式的基本设计思想是基于I/O复用模型来实现的。
咱先看下I/O复⽤模型,它和传统IO多线程阻塞不同,I/O复⽤模型中多个连接共⽤⼀个阻塞对象,应⽤程序只需要在⼀个阻塞对象等待。当某个连接有新的数据可以处理时,操作系统通知应⽤程序,线程从阻塞状态返回,开始进⾏业务处理。
接着上面的例子,餐厅⽼板也发现了顾客点餐慢的问题,于是他采⽤另外一种方式,就是只留了⼀个服务员,当客⼈点餐的时,这个服务员就去招待别的客⼈,客⼈点好餐后直接喊服务员来进⾏服务。这时的顾客和服务员可以分别看作多个连接和⼀个线程。服务员阻塞在⼀个顾客那⾥,当有别的顾客点好餐后,他就⽴刻去服务其他的顾客。
我们了解reactor的设计思想后,再来看下reactor单线程的实现⽅案:
Reactor通过I/O复⽤程序监控客户端请求事件,收到事件后通过任务分派器进行分发。之后建⽴连接请求事件,是通过Acceptor处理,并建立对应的handler负责后续业务处理。其它非连接事件,Reactor会调⽤对应的handler完成read->业务处理->write处理流程,并将结果返回给客户端,以上操作都在⼀个线程里完成。
⼆、单线程时代
Redis是基于Reactor单线程模式来实现的。
IO多路复⽤程序接收到⽤户的请求后,全部打到⼀个队列⾥,交给文件分派器,对于后续的操作,和在reactor单线程实现方案看到的⼀样,整个过程都在一个线程里完成,因此Redis被称为是单线程的操作。
对于单线程的Redis来说,基于内存且命令操作时间复杂度低,因此读写速率很快。
三、多线程时代
Redis6中引入了多线程,咱们都知道Redis单线程处理有着很快的速度,那为啥还要引⼊多线程呢?单线程的瓶颈在哪里?
我们先来看第二个问题,Redis中,单线程的性能瓶颈主要在⽹络IO操作上。也就是在读写请求调用执行会占用大部分CPU时间。这时我们要对一些大的键值对进⾏删除操作的话,在短时间内是删不完的,此时对于单线程来说就会阻塞后边的操作。
咱们想想Reactor模式中单线程的处理方式,在针对⾮连接事件,Reactor会调⽤对应的handler完成read->业务处理->write处理流程,这很明显这⼀步会造成性能上的瓶颈。Redis在设计上采⽤将⽹络数据读写和协议解析通过多线程的⽅式来处理,对于命令执⾏来说,仍然使⽤单线程操作。
四、总结一下
1、Reactor模式
(1)传统阻塞IO模型客户端与服务端线程1:1分配,不利于扩展。
(2)伪异步IO模型采用线程池,底层仍为同步阻塞,限制了最大连接数。
(3)Reactor通过I/O复用程序监控客户端请求,通过任务分派器进行分发。
2、单线程时代
(1)基于Reactor单线程模式实现,通过IO多路复⽤程序接收到⽤户的请求后,全部推送到⼀个队列⾥,交给⽂件分派器进⾏处理。
3、多线程时代
(1)单线程性能瓶颈主要在⽹络IO上。
(2)将⽹络数据读写和协议解析通过多线程的⽅式来处理,对于命令执⾏来说,仍然使⽤单线程操作。
以上所有仅代表个人认知,侵权请联系删除。