- 用户空间和内核空间
- IO
- 五种IO模型
- 阻塞IO
- 非阻塞IO
- IO多路复用
- select
- poll
- epoll
- web服务流程
- 信号驱动IO
- 异步IO
- IO模型比较
- redis网络模型
- redis为什么是单线程
- redis单线程网络模型流程
用户空间和内核空间
为安全,将用户应用和系统应用分隔开,产生用户空间和内核空间
32位操作系统,内存大小为2^32(4G)
IO
用户空间和内核空间各有一个缓冲区
- 写数据:数据->用户缓冲区->内核缓冲区->写入设备
- 读数据:设备->内核缓冲区->用户缓冲区->读出数据
读数据时,首先要等待数据到达内核缓冲区,其次从内核缓冲区拷贝数据到用户缓冲区
五种IO模型
阻塞IO
进程阻塞,等待数据到达内核缓冲区(数据就绪)
非阻塞IO
多次查询数据是否就绪,立即返回结果,不阻塞线程。
多次查询并未提高进程性能,反而使得cpu使用率增加
IO多路复用
利用单个线程同时监听多个文件描述符FD,在某个文件就绪时得到通知,充分利用CPU,避免无效等待
select
- 用户空间创建要监听的FD集合,将要监听的FD的对应bit位设为1。拷贝到内核空间。
- 内核空间遍历FD集合,监听FD,阻塞等待,直到FD集合中有就绪数据,遍历FD集合,已就绪的不处理,未就绪的设为0,然后将FD集合拷贝到用户空间,覆盖原来的。此时FD集合中为1的是已就绪的,并告知共有几个问就绪。
- 用户空间遍历集合,找到已就绪的数据
poll
epoll
- 用户空间epoll_create生成eventpoll,拷贝到内核空间
- 用户空间epoll_ctl添加FD到内核空间的eventpoll的rbr中
- FD就绪则触发callback函数,将FD添加到rdlist中
- epoll_wait阻塞等待一段时间,检查rdlist是否有就绪数据,如果rdlist不为空,则返回rdlist的大小(就绪的文件数量)并将rdlist拷贝到用户空间,如果为空则再次epoll_wait
事件通知机制:调用epoll_wait,当FD就绪,得到通知 - LT,数据可读时,多次通知(每调用epoll_wait一次,得到一次通知)直到数据处理完成。epoll的默认模式。重复通知,效率↓。
- ET,一次通知(第一次调用epoll_wait,得到一次通知,之后不再通知)
eg:将rdlist拷贝到用户空间
先将rdlist的链表指针断开,判断事件通知机制模式
如果是ET,则开始拷贝,拷贝后删除内核空间数据,因指针断开之后rdlist为空,则下一次epoll_wait时不会通知
如果是LT,则开始拷贝,当没有拷贝完时,恢复指针将数据连在rdlist中,下一次epoll_wait时,rdlist仍有数据,继续通知,继续读- ET可以手动添加回rdlist
- LT 可以while循环,一次将数据拷贝完,无需再添加回rdlist。
读取FD集合时,当读不到了进程会阻塞等待,直到有新的数据,所以while循环无法结束。应当采用非阻塞方式,每次读取立即返回无论是否有值 - LT惊群现象: 数据一直存在rdlist中,每一次检查调用epoll_wait,都会显示有数据就绪,会唤醒等待的进程,多个进程都被唤醒,而然在最初的进程接收并处理完数据后,其他进程获取不到数据,所以根本无需唤醒。
web服务流程
信号驱动IO
当内核有FD就绪时,发出SIGIO信号通知用户。无需阻塞等待也无需轮询
- 大量IO,SIGIO信号放在队列,可能导致队列溢出
- 用户空间和内核空间频繁信号交互,效率低
异步IO
- 高并发时,内核空间需要处理的数据过多,内存占用过大,系统崩溃
IO模型比较
同步异步看内核和用户空间拷贝过程
redis网络模型
redis为什么是单线程
核心业务部分(命令处理):单线程
核心网络模型:多线程,提高cpu利用率
- redis,纯内存操作,快
- 性能瓶颈为网络延迟,多线程没有帮助
- 多线程上下文切换有开销
- 多线程存在线程安全问题,需要引入锁,复杂度提高,性能降低
redis单线程网络模型流程
- serverSocket注册到aeEventLoop中监听,通过aeApiPoll(epoll_wait)检查serverSocket是否可读
- serverSocket可读,则由tcpAccpetHandler处理。得到clientSocket连接
- clientSocket注册到eventLoop中监听,通过aeApiPoll(epoll_wait)检查clientSocket是否可读,由readQueryFromClient处理
- 将clientSocket包装为一个client,读取数据放到client的buffer中,将buffer转为redis命令
- 将redis命令执行结果存储在client中(需要写回client客户端),放在队列里
- clientSocket可写时,将队列中的数据写入
- aeEventLoop到aeApiPoll == IO多路复用+事件派发
- 从clientSocket读写数据,受网络影响——>增加多线程