当前位置: 首页 > 科技观察

面试的时候说Redis是单线程的,被喷得很惨!

时间:2023-03-20 23:51:10 科技观察

本文转载自微信公众号《IT行业的农民工》,作者:吴来。转载本文请联系IT行业农民工公众号。Redis是单线程的。这是人尽皆知的道理。现在不一样了,Redis变了。再说这句话,我得带着质疑的语气跟你争论。意志不坚定的人,可以投降而随从他人。到底是个什么样子的读者,请和小赖一起往下读:-思维导图-Reactor模式Reactor模式,大家可能不太了解,如果看过前文,应该会有一点印象。说到Redis线程,就是一个绕不过去的话题。1.传统阻塞IO模型在说reactor模式之前,有必要提一下传统阻塞IO模型的处理方式。在传统的阻塞IO模型中,一个独立的Acceptor线程监听客户端的连接。每当客户端请求时,它都会分配一个新的线程供客户端处理。当多个请求同时到来时,服务器端会分配相应数量的线程。这样会导致CPU频繁切换,浪费资源。有些连接请求什么都不做,但服务器也会分配相应的线程,这样会造成不必要的线程开销。就像你去饭店吃饭,看菜单看了半天,发现真他妈贵,然后就走了。在这段时间里,等待你点菜的服务员相当于一个对应的线程。如果要点餐,可以看作是连接请求。同时,每次连接建立后,当线程调用读写方法时,线程会被阻塞,直到有数据可读和可写,期间线程不能做其他事情。还是上面那个餐厅吃饭的例子。你出去转转,发现还是这家餐厅性价比最高。回到这家餐厅,看了很久菜单,服务员也在等你点完。在这个过程中,服务员除了等待什么也做不了。这个过程相当于阻塞。你看这样,每次有请求过来,都要分配一个线程,一直阻塞到线程处理完。有些请求只是过来连接,什么都不做,还得给它分配一个线程,需要大量的服务器资源。遇到高并发场景,不敢想象。对于连接数比较少的固定架构可以考虑。2、伪异步IO模型大家可能知道一个通??过线程池优化的方案,使用线程池和任务队列。这称为伪异步IO模型。当客户端访问时,将客户端的请求封装成任务,交给后端线程池处理。线程池维护着一个消息队列和多个活跃的线程来处理消息队列中的任务。这种方案避免了每次请求都创建一个线程导致线程资源耗尽的问题。但是底层还是同步阻塞模型。如果线程池中的所有线程都被阻塞,则无法再响应任何请求。所以这种方式会限制最大连接数,不能从根本上解决问题。我们继续以上面的餐厅为例。餐厅老板经营了一段时间后,客人就多了起来。餐厅原有的5名服务员,一对一的服务实在是应付不过来。于是老大采用了5个个人线程池的方法。服务生服务完一位顾客后,立即去服务另一位顾客。这时候,问题就出现了。有的客人点菜速度很慢,服务员要等很久才能等到客人点完。如果5个顾客都点的很慢,这5个服务员就得一直等下去,就会导致剩下的顾客没人服务的状态。这就是我们上面提到的线程池中的所有线程都被阻塞的情况。那么如何解决这类问题呢?别着急,Reactor模式即将问世。3.Reactor设计模式Reactor模式的基本设计思想是基于I/O多路复用模型实现的。这是I/O多路复用模型。不同于传统的IO多线程阻塞,I/O多路复用模型中的多个连接共享一个阻塞对象,应用程序只需要等待一个阻塞对象。当一个连接有新数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始业务处理。你是什??么意思?餐厅老板也发现了顾客点菜慢的问题,于是采取了大胆的办法,只留下一名服务员。顾客点单,服务员去招待其他顾客,顾客点单后直接叫服务员服务。这里的顾客和服务员可以分别看成是多连接和一个线程。女服务员卡在一个顾客那里,其他顾客点了,她就马上去服务其他顾客。了解了reactor的设计思想后,我们再来看看今天单reactor的单线程实现:Reactor通过I/O多路复用程序监听客户端请求事件,接收到事件后通过任务调度器进行分发。对于连接建立请求事件,通过Acceptor进行处理,建立相应的handler负责后续的业务处理。对于非连接事件,Reactor会调用相应的handler完成读->业务处理->写的过程,并将结果返回给客户端。整个过程在一个线程中完成。了解了单线程时代的Reactor模式后,大家可能会有疑问,这跟我们今天的话题有什么关系。你可能不知道的是,Redis是基于Reactor单线程模式实现的。IO多路复用程序收到用户的请求后,将它们全部推入一个队列,交给文件调度器。对于后续的操作,在reactor单线程实现中看到,整个过程在一个线程中完成,所以Redis被称为单线程操作。对于单线程Redis,它是基于内存的,命令操作时间复杂度低,所以读写速度非常快。在多线程时代,Redis6版本引入了多线程。上面说到Redis单线程处理速度非常快,为什么要引入多线程呢?单线程的瓶颈在哪里?我们先来看第二个问题。在Redis中,单线程的性能瓶颈主要在网络IO操作上。即大部分CPU时间会在读写网络的读写系统调用执行期间被占用。如果要删除一些大的键值对,短时间内无法删除,所以对于单线程来说,会阻塞后续操作。回忆一下上面提到的Reactor模式下的单线程处理方式。对于非连接事件,Reactor会调用相应的handler来完成读->业务处理->写的过程,也就是说这一步会造成性能瓶颈。Redis旨在通过多线程处理网络数据读写和协议解析。对于命令执行,仍然使用单线程操作。总结Reactor模式传统的阻塞IO模型客户端和服务端线程1:1分配,不利于扩展。伪异步IO模型采用了线程池的方式,但是底层还是采用了同步阻塞的方式,限制了最大连接数。Reactor通过I/O多路复用程序监听客户端请求事件,并通过任务调度器进行分发。单线程时代是基于Reactor单线程模式实现的。通过IO多路复用程序接收到用户的请求后,全部推入一个队列,交给文件调度器处理。多线程时代,单线程性能瓶颈主要在网络IO上。网络数据读写和协议解析采用多线程方式处理。对于命令执行,仍然使用单线程操作。