Redis做为一个基于key-value的NoSQL数据库,最显著的特色存取速度很是快,官方说能够达到10W OPS,可是Redis为什么这么快?算法
Redis使用C语言进行编写的,而Unix系统也是C语言实现,因此C语言是很是贴近操做系统的语言数据库
基于内存读写是Redis速度快的主要缘由,不进行数据同步的状况下,不从磁盘读取数据,没有IO。内存响应时间大约100ns数组
1).单线程避免了线程上下文切换以及同步加锁、解锁带来的消耗。服务器
2).单线程简化算法的实现网络
3).单线程也带来一个问题,阻塞,对于一个长命令来讲,会阻塞不少命令的执行响应数据结构
这里的单线程,不包含fork()产生的子进程。除了Redis以外,Node.js、Nginx都是单线程,都属于高性能的组件框架并发
因为Redis是单线程的,全部的操做都是串行执行,可是因为读写操做等待用户输入或输出都是阻塞的,因此 I/O 操做在通常状况下每每不能直框架
接返回,这会致使某一文件的 I/O 阻塞致使整个进程没法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现的。高并发
Redis的I/O模型基于epoll实现,也提供select和kqueue的实现,默认epoll性能
epoll相对于其余多路复用技术,具备的优势:
1. epoll没有最大并发链接的限制,上限是最大能够打开文件的数目,这个数字通常远大于 2048
2. 效率提高,epoll最大的优势就在于它只管你“活跃”的链接,而跟链接总数无关,所以在实际的网络环境中, epoll的效率就会远远高于
select和poll
3. 内存拷贝,epoll在这点上使用了“共享内存 ”,这个内存拷贝也省略了。
epoll与select/poll的区别:
I/O多路复用:经过一种机制,能够监视多个描述符(File Descriptor,简称fd),一旦某个描述符就绪,可以通知程序进行相应的操做。
一、select:
本质是采用32个整数的32位,即32*32 = 1024来标识,fd值为1-1024。当fd的值超过1024限制时,就必须修改FD_SETSIZE的大小。这个时候就
能够标识32*max值范围的fd。
二、poll:
poll与select不一样,经过一个pollfd数组向内核传递须要关注的事件,故没有描述符个数的限制,pollfd中的events字段和revents分别用于
标示关注的事件和发生的事件,故pollfd数组只须要被初始化一次。
三、epoll:
是poll的一种优化,返回后不须要对全部的fd进行遍历,在内核中维持了fd的列表。select和poll是将这个内核列表维持在用户态,而后传递
到内核中。与poll/select不一样,epoll再也不是一个单独的系统调用,而是由epoll_create/epoll_ctl/epoll_wait三个系统调用组成,后面将会看到
这样作的好处。epoll在2.6之后的内核才支持。
select/poll的几大缺点:
一、每次调用select/poll,都须要把fd集合从用户态拷贝到内核态,这个开销在fd不少时会很大
二、同时每次调用select/poll都须要在内核遍历传递进来的全部fd,这个开销在fd不少时也很大
三、针对select支持的文件描述符数量过小了,默认是1024
4.select返回的是含有整个句柄的数组,应用程序须要遍历整个数组才能发现哪些句柄发生了事件;
5.select的触发方式是水平触发,应用程序若是没有完成对一个已经就绪的文件描述符进行IO操做,那么以后每次select调用仍是会将这些文
件描述符通知进程。相比select模型,poll使用链表保存文件描述符,所以没有了监视文件数量的限制,但其余三个缺点依然存在。
epoll IO多路复用模型实现机制:
因为epoll的实现机制与select/poll机制彻底不一样,上面所说的 select的缺点在epoll上不复存在。
epoll没有这个限制,它所支持的FD上限是最大能够打开文件的数目,这个数字通常远大于2048,举个例子,在1GB内存的机器上大约是10万左右
设想一下以下场景:有100万个客户端同时与一个服务器进程保持着TCP链接。而每一时刻,一般只有几百上千个TCP链接是活跃的(事实上大部
分场景都是这种状况)。
如何实现这样的高并发?
在select/poll时代,服务器进程每次都把这100万个链接告诉操做系统(从用户态复制句柄数据结构到内核态),让操做系统内核去查询这些套
接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,所以,
select/poll通常只能处理几千的并发链接。
若是没有I/O事件产生,咱们的程序就会阻塞在select处。可是依然有个问题,咱们从select那里仅仅知道了,有I/O事件发生了,但却并不知
道是那几个流(可能有一个,多个,甚至所有),咱们只能无差异轮询全部流,找出能读出数据,或者写入数据的流,对他们进行操做。可是使用
select,咱们有O(n)的无差异轮询复杂度,同时处理的流越多,每一次无差异轮询时间就越长
epoll的设计和实现与select彻底不一样。epoll经过在Linux内核中申请一个简易的文件系统(文件系统通常用什么数据结构实现?B+树)。把原先的
select/poll调用分红了3个部分:
1)调用epoll_create()创建一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
2)调用epoll_ctl向epoll对象中添加这100万个链接的套接字
3)调用epoll_wait收集发生的事件的链接
如此一来,要实现上面说是的场景,只须要在进程启动时创建一个epoll对象,而后在须要的时候向这个epoll对象中添加或者删除链接。同时,
epoll_wait的效率也很是高,由于调用epoll_wait时,并无一股脑的向操做系统复制这100万个链接的句柄数据,内核也不须要去遍历所有的链接。
epoll内容原文地址:http://www.javashuo.com/article/p-ocfrejfu-ng.html