Redis为什么那么快?
redis是在内存上的操作;redis是单进程、单线程、单实例的;还有就是多路复用,非阻塞IO;
多路复用:I/O多路复用指通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
在Linux世界中,一切皆文件,所有的IO设备都被抽象为一个文件概念;磁盘,网络,终端,甚至进程间通信工具管道都被当做文件对待,所有的IO操作都通过文件读写来实现;文件描述符(File description)就像是每一个文件的唯一标识,用来标注一个特定的文件
BIO/NIO/AIO
BIO:同步阻塞IO,一个进程或者线程对应处理内核的一个FD,当FD变为就绪状态的时候,进行处理,否则就一直阻塞状态,比较耗费连接和线程资源,Socket在这个时期是阻塞的;
NIO:同步非阻塞IO,用户空间中的一个连接循环读取FD,看是否就绪状态,有多少个FD就循环多少次,一个连接不只是为一个FD服务,读取一个不可用就继续读取下一个,不会阻塞;
AIO:异步非阻塞IO,所谓异步就是一个只负责读取看是否就绪状态,如果就绪交给别的处理线程去处理就行;
多路复用的几种实现方式
- select
select它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。另 外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量 TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
- poll
它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另 外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将 再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。
- epoll
直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll 同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的 值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在 系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后, 内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类 似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
总结一下:select是Linux内核的一个系统调用,从用户空间到内核携带所有FD去内核请求系统调用,select把就绪态返回回去,这样FD来回拷贝比较浪费资源,并且select数据结构是数组,携带最大FD是定值1024可以通过修改Linux内核重新编译进行修改,于是poll出现,poll只是不限制数量,其他和select一样,最后出来的是epoll,epoll让内核和用户空间有一片共享空间,不用把FD搬来搬去了,因此效率提升了,这里使用了内存映射(mmap)技术。