众所周知,Redis 之前的版本一直都是典型的单线程模型(注意:这里不是指 Redis 单实例中只有一个线程,而是表示核心操作模块由单线程完成,当然另外还有一些辅助线程,比如 LRU的淘汰过程等)。一个经常问到的问题,为什么Redis单线程为什么还那么快呢?要先从Reactor模式说起。
一、Reactor模型
Reactor模型,也叫反应器模型,是一种多线程模型。我们先来看看多线程模型的发展历史。
1、传统阻塞IO模型
在传统阻塞IO模型中,由一个独立的 Acceptor 线程来监听客户端的连接,每当有客户端请求过来时,它就会为客户端分配一个新的线程来进行处理。当同时有多个请求过来,服务端对应的就会分配相应数量的线程。这就会导致CPU频繁切换,浪费资源。
然而,有的连接请求过来不做任何事情,但服务端还会分配对应的线程,这样就会造成不必要的线程开销。同时,每次建立连接后,当线程调用读写方法时,线程会被阻塞,直到有数据可读可写,在此期间线程不能做其它事情。这就好比你去餐厅吃饭,拿着菜单看了半天,服务员也在旁边等你点完菜为止。这个过程中服务员什么也不能做,只能这么干等着,这个过程相当于阻塞。你拿着菜单看了半天发现真他娘的贵,然后你就走人了。这段时间等你点菜的服务员就相当于一个对应的线程,你要点菜可以看作一个连接请求。这段时间消耗的资源完全浪费掉了。
2、伪异步IO模型
于是有了一种通过线程池优化的解决方案,采用线程池和任务队列的方式。这种被称作伪异步IO模型。当有客户端接入时,将客户端的请求封装成一个 task 投递到后端线程池中来处理。线程池维护一个队列和多个活跃线程,对队列中的任务进行处理。这种解决方案,避免了为每个请求创建一个线程导致的线程资源耗尽问题。但是底层仍然是同步阻塞模型。如果线程池内的所有线程都阻塞了,那么对于更多请求就无法响应了。因此这种模式会限制最大连接数,并不能从根本上解决问题。
3、Reactor设计模式
Reactor 模式的基本设计是基于I/O多路复用技术来实现的。Reactor 通过 I/O复用程序监控客户端请求事件,收到事件后通过任务分派器进行分发。针对建立连接请求事件,通过 Acceptor 处理,并建立对应的 handler 负责后续业务处理。针对非连接事件,Reactor 会调用对应的 handler 完成 read->业务处理->write 处理流程,并将结果返回给客户端。整个过程都在一个线程里完成。
二、单线程Redis为什么快
到这里其实答案已经明朗了,Redis 就是是基于 Reactor 单线程模式来实现的。IO多路复用程序接收到用户的请求后,全部推送到一个队列里,交给文件分派器。对于后续的操作,和在 Reactor 单线程实现方案里一样,整个过程都在一个线程里完成,因此 Redis 被称为是单线程的操作。对于单线程的 Redis 来说,读写很快可总结为以下几点:
1、基于多路复用技术,没有阻塞
2、单线程没有线程间切换消耗
3、单线程程序可以设计的很简单,性能好
4、且基于内存,读写速率是非常快的。
使用了单线程后,可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。
Redis通过AE事件模型以及IO多路复用等技术,处理性能非常高,因此没有必要使用多线程。单线程机制使得 Redis 内部实现的复杂度大大降低,一些如LPUSH/LPOP等 “线程不安全” 的命令都可以无锁进行。
到这里问题似乎可以了结了,然而最新的Redis6 版本中却声称引入了多线程,这又是怎么回事呢?下一篇再说吧!