epoll、poll和select
在面试中,这三者有时候会一起问,也就是让你分析三种模型,并且解释三者的优劣。
先来看select,发起select调用的时候会传给select一堆代表连接的文件描述符,内核会帮你检查这些文件描述符。
它和epoll的区别是,你必须发起select调用,内核才会一个个帮你问。也就是说,select调用缺乏epoll那种即使你不调用epoll_wait,epoll也会把你准备好的文件描述符放到就绪列表的机制。一句话来说就是:epoll 会提前帮你准备好符合条件的文件描述符,但是 select 不会。
readfds = [] // 一堆文件描述符,作为候选
writefds = [] // 也是一堆文件描述符,作为候选
execpfds = [] // 还是一堆文件描述符,作为候选
select(readfds, writefds, excepfds) // 从这些描述符里面挑出符合条件的
在select方法内部,内核会遍历你传入的这些候选文件描述符,找出你要的。
poll和select的基本原理一样。
面试的时候可以强调一下和性能有关的几个点。
在面试中你主要面 epoll 的细节,poll 和 select 你大概提一下就可以。一般情况下你能解释清楚 epoll,就能赢得竞争优势了。在搞清楚了 Redis 使用的系统调用之后,还有一个面试的点,就是 Redis 使用的 Reactor 模式。
Reactor模式
Reactor模式也是广泛使用的IO模式,它的性能很好,Redis也用了Reactor模式。用一句话来说明Reactor模式:一个分发器 + 一堆处理器。
一般来说,客户端和服务端的IO交互主要有两类事件:连接事件和读写事件。那么Reactor里面的分发器就是把连接事件交给Acceptor,把读写事件交给对应的Handler。这些Handler最终会调用你真正需要读写数据的业务代码。
结合前面讲的epoll,你基本上就能猜到,Redis的Reactor就是调用了epoll,拿到创建连接的套接字,或是可读写的套接字,转发给后面的Acceptor或Handler。
在搞清楚这一点之后,接下来你就能够理解各种Reactor的变种了。变种基本上可以分为三类。
把Accetor做成多线程
把Handler做成多线程
把Reactor做成多线程。主线程只监听连接创建的事件,监听到了就交给其他线程处理。其他线程则是监听读写事件,然后调用对应的Handler处理。
Redis的特殊之处在于,Redis是单线程的。也就是说,Reactor、Handler、Acceptor都只是一个逻辑上的区分,实际上是同一个线程。所以当面试官问到的时候,把两者结合在一起回答。
为了保证性能最好,Redis使用的是基于epoll的Reactor模式。
Reactor模式可以看成一个分发器 + 一堆处理器。Reactor模式发起epoll之类的系统调用,如果是读写事件,那么就交给Handler处理;如果是连接事件,就交给Acceptor处理。
然后强调一下单线程的Redis是怎么使用这个Reactor模式的。
Redis是单线程模型,所以Reactor、Handler和Acceptor其实都是这个线程。
整个过程是这样的:
Redis中的Reactor调用epoll,拿到符合条件的文件描述符。
假如说Redis拿到了可读写的描述符,就会执行对应的读写操作。
如果Redis拿到了创建连接的文件描述符,就会完成连接的初始化,然后准备监听这个连接上的读写事件。
后面在 6.0 的时候,Redis 改成了多线程模型,但是基本原理还是 Reactor + epoll。
最后,你提到了 Redis 的 6.0 新模型,那么面试官就可能会问你两个问题。
同样是基于内存的缓存中间件,为什么 Memcache 用的是多线程模型,而 Redis 用的是单线程模型?
Redis 为什么最终又引入了多线程模型?和原本的单线程模型比起来,区别在哪里?