问题:为什么 Redis 单线程模型依然效率很⾼:
redis 是纯内存操作,读写速度非常快,所有 的从操作都会在内存中完成,不涉及任何I/O操作,因此多线程频繁的上下文切换反而是一种负优化。Redis 选择基于非阻塞I/O 的I/O 多路复用机制,在单线程里面并发处理客户端多个连接,减少线程切换带来的系统开销,同时也可以更好的维护性,方便开发和调试。
I/O同步和异步的区别在于:将数据从内核复制到用户区 的时候,用户进程是否会阻塞。
I/O阻塞和非阻塞的区别在于:进程发起系统调用,是会被挂起知道数据返回,还是之间返回成功和失败。
I/O模型的类别:阻塞I/O,非阻塞I/O,I/O复用模型,信号驱动I/O,异步I/O模型。
水平触发:当文件描述符就绪的时候会触发通知,如果程序没有一次读完,缓冲区有数据的时候,下次还会触发。select 只能支持水平触发。
边缘触发:仅当文件描述符从未就绪变成就绪的时候,通知一次,之后不会再通知。epoll 模型支持水平触发和边缘触发。当循环读取rec到没有数据就会出现eagain 现象。(LT 是多线程的时候可能会出现,默认跳过处理)
针对网络IO的操作,可以分为两个阶段:
准备阶段(阻塞):等待数据是否可用,是在内核进程完成的
操作阶段(同步):执行实际的IO调用,数据从内核缓冲区拷贝到用户进程缓冲区。
阻塞I/O:应用程序调用I/O操作的时候(如某个函数)阻塞,只有等待要操作的数据准备好,并复制到进程的缓冲区中(函数返回了)才返回。
非阻塞I/O:应用进程调用I/O 操作的时候,不会阻塞等待I/O返回,而是可以执行其他的任务。I/O 操作完成的时候,可以通过回调函数或者轮询的方式,获取结果,以便可以进一步执行操作。
I/O多路复用:多路I/O共用一个阻塞接口,此时阻塞发生在select/poll的系统调用上,而不是阻塞在实际的I/O系统调用。IO多路复用的高级之处在于可能支持等待多个文件描述符。而这些文件描述符的人一个进入就绪状态,select等函数就可以返回。
信号驱动的IO:注册一个IO事件,在数据可操作时候通过信号通过线程。
异步IO:应用进程通知内核开始一个异步I/O 异步操作,并让内核在整个操作完成后通知应用进程。
I/O 复用模型:
select 缺点:1.性能开销大,调用select 会陷入内核,这时会将参数中fd_set从用户空间拷贝到内核空间,内核空间需要遍历传递进来的fd_set的每一位,不管他们是否就绪。
2.能够箭筒的文件描述符数量太少:受限于sizeof(fd_set) 的大小,在编译内核时候就确定了且无法改,一般1024。
poll: poll 相比select 文件描述符数量大于1024.poll 在用户态通过数组的方式传递文件描述符,在内核会转为链表方式存储,没有最大数量的限制。
epoll:select ,poll 模型只用了一个函数,而epoll有三个函数,epoll_create,epoll_ctl,epoll_wait(相当于select,返回就绪数量)
特点:
1.使用红黑树存储文件描述符
2.使用队列存储就绪的文件描述符
3.每个文件描述符只需要在添加时传入一次,之后调用epoll_wait不需要再次传递,提高效率。文件描述符的数量远大于1024。
三者的区别总结:1.select,poll 都是在用户态情况下维护文件描述符集合,因此每次需要将完整的集合传给内核;epoll由操作系统维护文件描述符,因此只需要在创建的时候传入文件描述符(把文件描述符从用户区传递到内核区一次即可)。
2.当连接数比较多,并且很多不活跃的时候,epoll的效率高很多,当连接数较少,并且十分活跃的情况下,由于epoll需要很多回调,因此性能坑低于其他两者。
Reactor 和Proactor的区别:
Reactor 是阻塞同步网络操作,感知的是就绪可读写事件。在每次感知有IO事件发生(比如可读就绪事件)后,就需要应用进程主动调用read方法完成来完成数据的读取,也就是要应用进程主动将socket接受缓存中的数据读到应用程序中,这个数据是同步的,只有读取完数据才能处理数据。
Proactor 是异步网络模型,感知的是已经完成的**读写事件**。在发起异步读取请求时,需要传入数据缓冲区的地址等信息。这样内核才能自动帮我们把数据读写工作完成,这里的读写工作完全有操作系统来做,并不像Reactor 会主动发起read、write来读写数据,操作系统完成读写工作之后就会调用应用进程处理数据。
因此Reactor可以理解为【事件来了操作那个系统,通知应用程序让应用程序来处理】,Proactor可以理解为【操作系统来处理,处理再在通知应用程序】。这里的事件是指有新链接,有数据可读,有数据科可写的I/O 事件。这里的【处理】包含从驱动读取到内核以及从内核读取到用户空间。无论是Reactor 和Proactor 都是基于事件分发的网络编程模型。区别是Reactor 是基于【待完成】的I/O 事件,而Proactor模型则是基于【已完成】的I/O事件。