redis 的 I/O 多路复用
概念
针对多个来源的 i/o 请求,采用事件驱动模式,主线程可以视作是一个状态机,监听收到的各种 i/o 请求,若是没有请求,则阻塞等待,有请求就将输入的请求和请求状态转移到一个输出状态。
实际就是针对 i/o 使用了 reactor 的思想,主程序将事件以及对应事件处理的方法在Reactor
上进行注册, 如果相应的事件发生,Reactor
将会主动调用事件注册的接口,即回调函数.
一般用 select,poll,epoll 来做 i/o 事件监听的函数,下面让我们来看看他们的区别。
redis 的 I/O 多路复用实现
单线程 redis 快的原因
1.纯内存操作
2.单线程操作
3.采用非阻塞 I/O 多路复用
这里主要来说一下 redis 的非阻塞 I/O 多路复用的实现
框架
客户端连接到服务器会有许多个,他们会执行很多不同的命令,如应答,写入,读取,关闭等操作,因此会存在一个服务器连接了多个套接字且这些事件并发出现的场景。
所有套接字统一进入 redis 的多路复用程序,多路复用程序将这些套接字放入一个套接字队列,然后有序,同步,每次一个的将套接字传给文件事件分派器。
分派器接收到套接字,并根据套接字产生时间的类型,调用相应的事件处理器。
事件处理器就是一个个函数,他们定义了某个事件发生时候服务器应该执行的操作。
i/o 复用程序的实现
redis 的 i/o 复用程序底层实现了 select,epoll,evport 和 kqueue 这些 i/o 多路复用函数库,他们实现了相同的 api,所以底层实现可以互换(工厂模式)。
编译的时候自动选择系统中性能最高的 i/o 多路复用函数库来作为 i/o 复用的底层实现。
选择依据如下
按照 i/o 复用方法的性能从高到底排序,evport->epoll->kqueue->select
i/o 复用算法的区别
除了效率以外,i/o 算法的选择与操作系统也有一定的关系
evport -> Solaries 10
epoll -> linux 系统
kqueue -> macOS/FreeBSD
select -> 所有系统
其中 evport,epoll,kqueue 复用函数的选择都是时间复杂的 O(1),使用了内核内部的结构,并且能够服务几十万的文件描述符,而 select 是 O(n),且最多同时服务 1024 个文件描述符。
这里针对 epoll 和 select 做一个简单对比
epoll 为什么高效
1.epoll 创建对象的时候在内核注册了一个文件系统用于存储被监控的 soket,在内核 cache 里会额外建了个红黑树用于存储以后 epoll_ctl 传来的 socket 支持快速查找插入删除,还会再建立一个 list 链表,用于存储准备就绪的事件。
2.epoll_ctl 用于把 socket 放到 epoll 文件系统里 file 对象对应的红黑树上,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪 list 链表里。所以,当一个 socket 上有数据到了,内核在把网卡上的数据 copy 到内核中后就来把 socket 插入到准备就绪链表里了。
3.epoll_wait 查看在 create 阶段创建的 list 列表是否有数据,有数据就返回,没有就等待,所以是 O(1)
select 为什么相对低效(poll 与 select 基本一致)
1.每次调用 select 都需要在内核遍历传递进来的所有 fd
2.内核/用户数据拷贝频繁,操作复杂,每次调用 select,都需要把 fd 集合从用户态拷贝到内核态
3.select 最多支持 1024 个文件描述符,poll 本质没有区别只是没有最大连接数限制(因为基于链表存储)
参考文档:
https://zhuanlan.zhihu.com/p/93612337(reactor 模式)
深入理解计算机系统
https://www.cnblogs.com/aspirant/p/9166944.html(select,poll,epoll 区别)
https://m.php.cn/redis/424900.html(redis 单线程为什么快)
redis 设计与实现
https://blog.csdn.net/tanswer_/article/details/70196139
https://blog.csdn.net/zgege/article/details/81632990(select,poll
,epoll 区别)
评论