redis服务端对于命令的处理是单线程的,但是在i/o层面却可以同时面对多个客户端并发的提供服务,并发到内部单线程的转化通过多路复用框架实现
一个io操作的完整流程是数据请求先从用户态到内核态,也就是操作系统层面,然后再调用操作系统提供的api,调用相对应的设备去获取相应的数据。
当相应的设备准备好数据后,会将数据复制到内核态,处理方式分为阻塞和非阻塞
阻塞:用户请求会等待数据从操作系统调用相应的设备返回到内核态,如果没有返回则处于阻塞状态
非阻塞:操作系统接收到一组文件描述符,然后操作系统批量处理这些文件描述符,然后不管有没有准备好数据都立即返回,如果没有对应的准备好的文件描述符,则继续轮询获取准备好数据的文件描述符。
数据从内核态复制到用户态的处理方式又分为同步和异步
同步:用户请求等待数据从内核态向用户态复制数据,在此期间不做其他事情
异步:在数据从内核态向用户态复制的过程中,用户请求不会一直处于等待状态而是做其他事情
redis的多路复用框架使用的非阻塞的数据返回模式
模型:select、poll、epoll
redis 是一个单线程却性能非常好的内存数据库, 主要用来作为缓存系统。 redis 采用网络io多路复用技术来保证在多连接的时候, 系统的高吞吐量。
为什么 redis 中要使用 i/o 多路复用这种技术呢?
首先,redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 i/o 操作在一般情况下往往不能直接返回,这会导致某一文件的 i/o 阻塞导致整个进程无法对其它客户提供服务,而 i/o 多路复用就是为了解决这个问题而出现的。
redis的io模型主要是基于epoll实现的,不过它也提供了select和kqueue的实现,默认采用epoll。
那么epoll到底是个什么东西呢? 其实只是众多i/o多路复用技术当中的一种而已,但是相比其他io多路复用技术(select, poll等等),epoll有诸多优点:
1. epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048,
一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 察看。
2. 效率提升, epoll 最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中, epoll 的效率就会远远高于 select 和 poll 。
3. 内存拷贝, epoll 在这点上使用了“共享内存”,这个内存拷贝也省略了。
那么epoll的原理是什么呢?
epoll的系统调用很简单,只有三个,其定义如下:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
epoll的实现原理就是基于这三个函数来实现的,具体步骤如下:
首先,需要调用epoll_create来创建一个epoll的文件描述符,内核会同时创建一个eventpoll的数据结构。这个数据结构里面会包含两个东西,一个是红黑树,专门用于存储epoll_ctl注册进来的fd文件描述符;另外一个是就绪链表,用来存储epoll_wait调用相关的,已经就绪的那些fd文件描述符。
struct eventpoll{
struct rb_root rbr; // 红黑树的根节点,存储着所有添加到epoll中的需要监控的事件
struct list_head rdlist;// 双链表中存放着将要通过epoll_wait返回给用户的满足条件的事件
};
其次,因为epoll中的所有事件,都与网卡驱动程序建立回调关系,当相应的事件发生的时候,会通过这个回调函数,将发生的事件添加到就绪链表当中。
最后,当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有需要处理的事件。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。