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

Redis是单线程得 , 这话搁以前 , 是横着走的 , 谁都知道的真理 。 现在不一样 , Redis 变了 。 再说这句话 , 多少得有质疑的语气来跟你辩驳一番 。 意志不坚定的 , 可能就缴械投降 , 顺着别人走了 。
到底是什么样的 , 各位看官请跟小莱一起往下看:
面试时说Redis是单线程的,被喷惨了文章插图
Reactor模式
反应器模式 , 你可能不太认识 , 如果看过上篇文章的话应该会有点印象 。 涉及到 Redis 线程它是一个绕不过去的话题 。
1、传统阻塞IO模型
在讲反应器模式前 , 这里有必要提一下传统阻塞IO模型的处理方式 。
在传统阻塞IO模型中 , 由一个独立的 Acceptor 线程来监听客户端的连接 , 每当有客户端请求过来时 , 它就会为客户端分配一个新的线程来进行处理 。 当同时有多个请求过来 , 服务端对应的就会分配相应数量的线程 。 这就会导致CPU频繁切换 , 浪费资源 。
有的连接请求过来不做任何事情 , 但服务端还会分配对应的线程 , 这样就会造成不必要的线程开销 。 这就好比你去餐厅吃饭 , 你拿着菜单看了半天发现真他娘的贵 , 然后你就走人了 。 这段时间等你点菜的服务员就相当于一个对应的线程 , 你要点菜可以看作一个连接请求 。
面试时说Redis是单线程的,被喷惨了文章插图
同时 , 每次建立连接后 , 当线程调用读写方法时 , 线程会被阻塞 , 直到有数据可读可写 ,在此期间线程不能做其它事情 。 还是上边餐厅吃饭的例子 , 你出去转了一圈发现还是这家性价比最高 。 回到这家餐厅又拿着菜单看了半天 , 服务员也在旁边等你点完菜为止 。 这个过程中服务员什么也不能做 , 只能这么干等着 , 这个过程相当于阻塞 。
面试时说Redis是单线程的,被喷惨了文章插图
你看这样的方式 , 每来一个请求就要分配一个线程 , 并且还得阻塞地等线程处理完 。 有的请求还只是过来连接下 , 什么操作也不干 , 还得为它分配一个线程 , 对服务器资源要求那得多高啊 。 遇到高并发场景 , 不敢想象 。 对于连接数目比较小的的固定架构倒是可以考虑 。
2、伪异步IO模型
你可能了解过一种通过线程池优化的解决方案 , 采用线程池和任务队列的方式 。 这种被称作伪异步IO模型 。
当有客户端接入时 , 将客户端的请求封装成一个 task 投递到后端线程池中来处理 。 线程池维护一个消息队列和多个活跃线程 , 对消息队列中的任务进行处理 。
面试时说Redis是单线程的,被喷惨了文章插图
这种解决方案 , 避免了为每个请求创建一个线程导致的线程资源耗尽问题 。 但是底层仍然是同步阻塞模型 。 如果线程池内的所有线程都阻塞了 , 那么对于更多请求就无法响应了 。 因此这种模式会限制最大连接数 , 并不能从根本上解决问题 。
我们继续用上边的餐厅来举例 , 餐厅老板在经营了一段时间后 , 顾客多了起来 , 原本店里的5个服务员一对一服务的话根本对付不过来 。 于是老板采用5个人线程池的方式 。 服务员服务完一个客人后立刻去服务另一个 。
这时问题出现了 , 有的客人点菜特别慢 , 服务员就得等待很长时间 , 直到客人点完为止 。 如果5个客人都点的特别慢的话 , 这5个服务员就得一直等下去 , 就会导致其余的顾客没有人服务的状态 。 这就是我们上边所说的线程池所有线程都被阻塞的情况 。
那么这种问题该如何解决呢?别急 ,Reactor 模式就要出场了 。
3、Reactor设计模式
Reactor 模式的基本设计思想是基于I/O复用模型来实现的 。
这里说下I/O复用模型 。 和传统IO多线程阻塞不同 , I/O复用模型中多个连接共用一个阻塞对象 , 应用程序只需要在一个阻塞对象等待 。 当某个连接有新的数据可以处理时 , 操作系统通知应用程序 , 线程从阻塞状态返回 , 开始进行业务处理 。
什么意思呢?餐厅老板也发现了顾客点餐慢的问题 , 于是他采用了一种大胆的方式 , 只留了一个服务员 。 当客人点餐的时候 , 这个服务员就去招待别的客人 , 客人点好餐后直接喊服务员来进行服务 。 这里的顾客和服务员可以分别看作多个连接和一个线程 。 服务员阻塞在一个顾客那里 , 当有别的顾客点好餐后 , 她就立刻去服务其他的顾客 。
了解了 reactor 的设计思想后 , 我们再来看下今天的主角单 reactor 单线程的实现方案: