腾讯面试被问得不知所措的一个问题

 

redis是一个单进程单线程的内存数据库,主要用来作为缓存系统。采用了网络io多路复用技术来保证在多连接的时候的系统的吞吐量

 

为什么redis使用io多路复用技术?

   因为redis是单线程的,所有的操作都是按照顺序线性执行的,但是由于读写操作是阻塞的,所以某个请求假如是阻塞的话那么整个进程都无法对其他客户端提供服务。所以就有了io多路复用

  redis的io多路复用模型是基于epoll实现的,多路复用技术还有select,poll。选择epoll实现的原因就是epoll有以下优点:

             1.epoll没有最大并发连接的限制,上限是最大可以打开文件的数目

             2.效率更高,epoll只管活跃的连接,而与连接总数无关

            3.内存使用上epoll使用了共享内存,所以内存拷贝也省略了

 其实io多路复用就是通过一种机制,可以监视多个描述符,一旦某个描述符就绪,就能够通知程序进行相应的操作(通过监视描述符,将对数据库的操作转换成了事件,从而减少了线程切换时上下文的切换和竞争)

 

epoll与select/poll的区别?

    select的本质是采用32位整数,所以描述符大小有限制

    poll通过一个pollfd数组向内核传递需要关注的事件,所以没有描述符个数的限制,它的pollfd数组只需要被初始化一次

    epoll是poll的一种优化,返回后不需要对所有的fd进行遍历,在内核中维持了fd的列表,它不是一个单独是系统调用,而是由epoll_create/epoll_ctl/epoll_wait三个系统调用组成

 

select/poll的缺点?

   1.每次调用select/poll,都需要把fd集合从用户态拷贝到内核态,当fd很多时开销大

   2.每次调用select/poll都需要在内核遍历传递进来的所有fd,fd很多时开销大

   3.select的文件描述符数量太小,默认1024

  4.select是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行io操作,那么之后的每次select调用还是会将这些描述符通知进程

  5.poll没有监视文件数量的限制,但是其他缺点还是存在

 

epoll io多路复用模型实现机制?

   首先假设一个场景:100万个客户端同时与1个服务器进程保持tcp连接,而每一时刻只有几百个tcp连接是活跃的,那么这么实现高并发?

    在用select/poll时,服务器进程每次把100万个连接告诉操作系统,让操作系统内核去查询这些套接字上是否有事件发生,轮询完成后再将数据复制到用户态,让服务器轮询处理,因为是无差别的轮询所有流,所以时间复杂度为O(n)

   而使用epoll后:epoll在内核中申请了一个简易的文件系统(B+树)实现的,把原来的select/poll调用分为了3部分:

                       1.调用epoll_create()建立epoll对象

                       2.调用epoll_ctl向epoll对象添加这100万个连接的套接字

                       3.调用epoll_wait收集发生事件的连接

    这样的话,只需要在进程启动时创建一个epoll对象,然后在需要的时候向这个epoll对象添加或删除连接就行了,调用epoll_wait时内核不需要去遍历全部的连接

 

epoll的底层实现?

   当某一个进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,用于存放通过epoll_ctl方法向对象中添加进来的事件,这些事件会挂载在红黑树上(红黑树的时间效率是logn),因此,比poll,select快得多

 

 

epoll的优点?

  1.不用重复传递。调用epoll_wait时相当于以前调用select/poll,但是不用传递socket给内核,因为内核已经在epoll_ctl中拿到了句柄列表

  2.在内核中,一切皆文件。epoll在被内核初始化时,同时会开辟出epoll自己的内核高速cache区,用于安置每一个我们想监控的socket,这些socket会以红黑树的形式保存在内核cache里,用以支持快速查找删除插入操作

  3.高效的原因在于:调用epoll_create时,内核除了在cache里建立了红黑树用于存储socket外,还建立了一个list链表用于存储准备就绪的事件,当epoll_wait调用时,只需要观察list链表里有没有数据即可,有就返回,没有就sleep,超时也返回

 

 

redis的高并发和高性能的原因?

     1.redis是纯内存操作,所以读取速度特别快

      2.redis采用了多路复用模型,这样的话就算某个请求是阻塞请求redis进程也能够服务于其他客户端

     3.redis是单线程的,没有线程上下文切换的开销

   4.redis使用的数据结构比如hash,读取速度很快,再比如跳表,也能加快读取速度