百万级连接和千万级连接的请求就是通过这些模型来做的
epoll:是nginx底层的机制了,运用事件驱动的方式支持千万级连接,像一些大厂使用的代理服务器就用nginx来做
一、select.poll的原理和优缺点:
上篇文章讲解了unix的五种网络编程模型
1、什么是IO多路复用:
I/O多路复用,I/O指的是网络I/O,就是客户端请求,到服务端响应,多路指多个TCP连接(即socket或者channel),复用指复用一个或几个线程。
简单来说:就是使用一个或者几个线程处理多个TCP连接,服务器创建线程和时间片的切换是很耗性能的。
最大优势是减少系统开销小,不必创建过多的进程/线程,也不必维护这些进程/线程。
上面的图中,每一个actor代表一个客户端,每一条线代表10万条请求连接,Linux内核相当于服务器。
然后Linux内核有多个进程/线程去处理连接数,轮询的去处理这些的连接数。IO的多路复用就是这种的意思。
Select:是多路复用的其中一种
基本原理:
监听文件3类描述符:writefds,readfds和exceptfds,所以在IO多路复用里面有写事件,读事件,异常事件。
在linux里面所有的文件的操作,socket连接都是一个文件描述符,在java编程里面,所有的东西都是对象,在Linux里面一切的东西都是fd,不是百分百,大部分都是这样的。
select的时候会调用一个函数,这个函数会监听用户的30万个连接,监听是否有读和写还有异常事件,调用后select函数会阻塞住,等有数据,可读,可写,或者出现异常,或者超时就会返回。select函数正常返回后,通过遍历30万个连接的请求的数组才能发现哪些句柄发生了事件(来判断哪些是可读或者可写的),来找到就绪的描述符fd,然后进行对应的IO操作。几乎在所有的平台上支持,跨平台支持性好。
这是最原始的多路复用技术。
缺点:
1):select采用轮询的方式扫描文件描述符,全盘扫描,随着文件描述符FD数量增多而性能下降。因为30万个请求的链接会在用户空间和linux内核空间拷来拷去,是很耗性能的。
2):每次调用 select(),需要把fd集合从用户态拷贝到内核态,并进行遍历(消息传递都是从内核到用户空间)
3):最大的缺陷就是单个进程打开的FD有限制,默认是1024(可修改宏定义,但是效率仍然慢) 宏定义:就是java定义的常量 static final int MAX_FD=1024000,
所以在select模型支持太多的并发是不可能的,最多有几千个,上万的话性能就会急剧的下降。
poll:
基本流程:
select()和poll()系统调用的大体一样,处理多个描述符也是使用轮询的方式,根据描述符的状态进行处理,一样需要把fd集合从用户态拷贝到内核态,并进行遍历
最大的区别是:poll没有最大文件描述符限制(使用链表的方式存储fd,因为链表可以无限的去扩展,单/双向链表都可以无限扩展)
高并发编程必备知识IO多路复用技术-epoll讲解
epoll模型是nginx的模型,nginx底层就可以用这个,可以支持千万级的连接并发
基本原理:在2.6内核中提出的,对比select和poll,epoll更加灵活,没有描述符限制,用户态拷贝到内核只需要一次使用,采用事件通知的方式而不是轮询,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用callback的回调机制来激活对应的fd,然后进行io事件的处理。
优点:
1):没fd这个限制,所支持的FD上限是操作系统的最大文件句柄数,这个最大句柄数是可以查看的,一般是65535,不用去在意它,一般是可以修改的,1G内存大概支持10万个句柄,虽然是65535,但是可以去调整它,一把把它调整成几十万,要支持单机百万连接的话,10G的话16G内存就可以解决了,所以单机解决百万的连接是不难的,只要模型用的对,Linux内核配的对就没问题。但是你要说网路IO阻塞了那是没有办法。
2):效率提高,使用回调通知而不是轮询的方式,不会随着增加FD的数目的增加而效率下降,前面的两种模型是随着FD的数目增加而下降 。采用回调机制callback
3):通过callback机制通知,内核和用户空间mmap同一块内存实现,不用拷贝来拷贝去,性能非常高。
当启动linux内核的时候:会涉及到下面三个函数
Linux内核核心函数:是c里面的,poll和select采用的是分别一个函数
1)epoll_create():在系统启动的时候,在linux内核里面申请一个文件系统:B+树,查找效率非常快,返回epoll对象,也是一个fd
2) epoll_ctl():操作epoll对象,在这个对象里面修改添加删除对应的链接fd,在epoll对象里面有100万个链接的话,假如有1万个链接是活跃的,1万个fd会就绪放到一个集合里面去,那么这1万个fd就会有对应的回调函数。
3) epoll_wait():在ctl操作epoll对象的时候,wait就会做对应的处理了。判断并完成对应的io操作,判断这个集合callback是否为空,不为空的话,对集合中的fd都做一个io的操作。
三个函数组成函数组成epoll的高性能,这三个是互相搭配的
缺点:
编程模型比select/poll复杂
举个例子:100百万个链接,里面有1万个链接是活跃的,在select,poll,epoll三个模型中表现是怎么样的?
在select模型里面:想要支持百万个链接的话,单个进程的话支持1024,在不修改宏定义的情况下该怎么做呢?则需要1000个进程才可以支持100万个连接,1000个进程在一台机器上创建的话给8核16G的话,这时机器会挂掉的,1000个进程每个进程处理1000个连接,cpu是轮询不过来的。性能会特别的差
poll:100万个链接的话,遍历都响应不过来了,还有空间的拷贝消耗大量的资源
epoll:不会遍历整个fd,通过上面的三个函数,第一个函数进行注册,注册好之后,100万个链接进来的时候,有1万个链接活跃,就会操作epoll对象,并绑定一个callback函数,当每一个fd就绪的时候就会触发callback函数,从而调用wait函数进行处理从而完成io的操作。省了两点:不用遍历fd,不用进行内核空间和用户空间之间的拷贝。