现在linux epoll 高并发单机(4G内存)并发量最大能达到多少?

更新:由于此文陆陆续续收到赞同,而且其中有些地方并不完全正确,特在本文最后予以订正&br&&br&我不了解楼主的层次,我必须从很多基础的概念开始构建这个答案,并且可能引申到很多别的问题。&br&&br&首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象。&br&不管是文件,还是套接字,还是管道,我们都可以把他们看作流。&br&之后我们来讨论I/O的操作,通过read,我们可以从流中读入数据;通过write,我们可以往流写入数据。现在假定一个情形,我们需要从流中读数据,&b&但是流中还没有数据&/b&,(典型的例子为,客户端要从socket读如数据,但是服务器还没有把数据传回来),这时候该怎么办?&br&&ul&&li&阻塞。阻塞是个什么概念呢?比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做);那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。&/li&&li&非阻塞&b&忙&/b&轮询。接着上面等快递的例子,如果用忙轮询的方法,那么你需要知道快递员的手机号,然后每分钟给他挂个电话:“你到了没?”&/li&&/ul&很明显一般人不会用第二种做法,不仅显很无脑,浪费话费不说,还占用了快递员大量的时间。&br&大部分程序也不会用第二种做法,因为第一种方法经济而简单,经济是指消耗很少的CPU时间,如果线程睡眠了,就掉出了系统的调度队列,暂时不会去瓜分CPU宝贵的时间片了。&br&&br&为了了解阻塞是如何进行的,我们来讨论缓冲区,以及内核缓冲区,最终把I/O事件解释清楚。缓冲区的引入是为了减少频繁I/O操作而引起频繁的系统调用(你知道它很慢的),当你操作一个流时,更多的是以缓冲区为单位进行操作,这是相对于用户空间而言。对于内核来说,也需要缓冲区。&br&假设有一个管道,进程A为管道的写入方,B为管道的读出方。&br&&ol&&li&假设一开始内核缓冲区是空的,B作为读出方,被阻塞着。然后首先A往管道写入,这时候内核缓冲区由空的状态变到非空状态,内核就会产生一个事件告诉B该醒来了,这个事件姑且称之为“缓冲区非空”。&/li&&li&但是“缓冲区非空”事件通知B后,B却还没有读出数据;且内核许诺了不能把写入管道中的数据丢掉这个时候,A写入的数据会滞留在内核缓冲区中,如果内核也缓冲区满了,B仍未开始读数据,最终内核缓冲区会被填满,这个时候会产生一个I/O事件,告诉进程A,你该等等(阻塞)了,我们把这个事件定义为“缓冲区满”。&/li&&li&假设后来B终于开始读数据了,于是内核的缓冲区空了出来,这时候内核会告诉A,内核缓冲区有空位了,你可以从长眠中醒来了,继续写数据了,我们把这个事件叫做“缓冲区非满”&/li&&li&也许事件Y1已经通知了A,但是A也没有数据写入了,而B继续读出数据,知道内核缓冲区空了。这个时候内核就告诉B,你需要阻塞了!,我们把这个时间定为“缓冲区空”。&/li&&/ol&这四个情形涵盖了四个I/O事件,缓冲区满,缓冲区空,缓冲区非空,缓冲区非满(注都是说的内核缓冲区,且这四个术语都是我生造的,仅为解释其原理而造)。这四个I/O事件是进行阻塞同步的根本。(如果不能理解“同步”是什么概念,请学习操作系统的锁,信号量,条件变量等任务同步方面的相关知识)。&br&&br&然后我们来说说阻塞I/O的缺点。但是阻塞I/O模式下,一个线程只能处理一个流的I/O事件。如果想要同时处理多个流,要么多进程(fork),要么多线程(pthread_create),很不幸这两种方法效率都不高。&br&于是再来考虑非阻塞忙轮询的I/O方式,我们发现我们可以同时处理多个流了(把一个流从阻塞模式切换到非阻塞模式再此不予讨论):&br&while true {&br&
for i in stream[]; {&br&
if i has data&br&
read until unavailable&br&}&br&}&br&我们只要不停的把所有流从头到尾问一遍,又从头开始。这样就可以处理多个流了,但这样的做法显然不好,因为如果所有的流都没有数据,那么只会白白浪费CPU。这里要补充一点,阻塞模式下,内核对于I/O事件的处理是阻塞或者唤醒,而非阻塞模式下则把I/O事件交给其他对象(后文介绍的select以及epoll)处理甚至直接忽略。&br&&br&为了避免CPU空转,可以引进了一个代理(一开始有一位叫做select的代理,后来又有一位叫做poll的代理,不过两者的本质是一样的)。这个代理比较厉害,可以同时观察许多流的I/O事件,在空闲的时候,&b&会把当前线程阻塞掉&/b&,当有一个或多个流有I/O事件时,就从阻塞态中醒来,于是我们的程序就会轮询一遍所有的流(于是我们可以把“忙”字去掉了)。代码长这样:&br&while true {&br&
select(streams[])&br&
for i in streams[] {&br&
if i has data&br&
read until unavailable&br&}&br&}&br&于是,如果没有I/O事件产生,我们的程序就会阻塞在select处。但是依然有个问题,我们从select那里仅仅知道了,有I/O事件发生了,但却并不知道是那几个流(可能有一个,多个,甚至全部),我们只能&b&无差别轮询&/b&所有流,找出能读出数据,或者写入数据的流,对他们进行操作。&br&但是使用select,我们有O(n)的无差别轮询复杂度,同时处理的流越多,每一次无差别轮询时间就越长。再次&br&&b&说了这么多,终于能好好解释epoll了&/b&&br&epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll之会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。(复杂度降低到了O(k),k为产生I/O事件的流的个数,也有认为O(1)的[更新 1])&br&在讨论epoll的实现细节之前,先把epoll的相关操作列出[更新 2]:&br&&ul&&li&epoll_create 创建一个epoll对象,一般epollfd = epoll_create()&br&&/li&&li&epoll_ctl (epoll_add/epoll_del的合体),往epoll对象中增加/删除某一个流的某一个事件&br&比如&br&epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//有缓冲区内有数据时epoll_wait返回&br&epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//缓冲区可写入时epoll_wait返回&/li&&li&epoll_wait(epollfd,...)等待直到注册的事件发生&br&&/li&&/ul&(注:当对一个非阻塞流的读写发生缓冲区满或缓冲区空,write/read会返回-1,并设置errno=EAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件)。&br&一个epoll模式的代码大概的样子是:&br&while true {&br&
active_stream[] = epoll_wait(epollfd)&br&
for i in active_stream[] {&br&
read or write till unavailable&br&}&br&}&br&限于篇幅,我只说这么多,以揭示原理性的东西,至于epoll的使用细节,请参考man和google,实现细节,请参阅linux kernel source。&br&======================================&br&[更新1]: 原文为O(1),但实际上O(k)更为准确&br&[更新2]: 原文所列第二点说法让人产生EPOLLIN/EPOLLOUT等同于“缓冲区非空”和“缓冲区非满”的事件,但并非如此,详细可以Google关于epoll的边缘触发和水平触发。
更新:由于此文陆陆续续收到赞同,而且其中有些地方并不完全正确,特在本文最后予以订正我不了解楼主的层次,我必须从很多基础的概念开始构建这个答案,并且可能引申到很多别的问题。首先我们来定义流的概念,一个流可以是文件,socket,pipe等等…
这个还是很好说清楚的。
&br&&br&假设你是一个机场的空管, 你需要管理到你机场的所有的航线, 包括进港,出港, 有些航班需要放到停机坪等待,有些航班需要去登机口接乘客。
&br&&br&你会怎么做?
&br&&br&最简单的做法,就是你去招一大批空管员,然后每人盯一架飞机, 从进港,接客,排位,出港,航线监控,直至交接给下一个空港,全程监控。
&br&&br&那么问题就来了:
&br&&ul&&li&很快你就发现空管塔里面聚集起来一大票的空管员,交通稍微繁忙一点,新的空管员就已经挤不进来了。 &br&&/li&&li&空管员之间需要协调,屋子里面就1, 2个人的时候还好,几十号人以后 ,基本上就成菜市场了。&br&&/li&&li&空管员经常需要更新一些公用的东西,比如起飞显示屏,比如下一个小时后的出港排期,最后你会很惊奇的发现,每个人的时间最后都花在了抢这些资源上。 &br&&/li&&/ul&&br&现实上我们的空管同时管几十架飞机稀松平常的事情, 他们怎么做的呢?
&br&他们用这个东西
&br&&img src=&/583d5ba3cee12e78befa8e2b749f4269_b.jpg& data-rawwidth=&550& data-rawheight=&534& class=&origin_image zh-lightbox-thumb& width=&550& data-original=&/583d5ba3cee12e78befa8e2b749f4269_r.jpg&&这个东西叫flight progress strip.
每一个块代表一个航班,不同的槽代表不同的状态,然后一个空管员可以管理一组这样的块(一组航班),而他的工作,就是在航班信息有新的更新的时候,把对应的块放到不同的槽子里面。&br&&br&&i&这个东西现在还没有淘汰哦,只是变成电子的了而已。。&/i&&br&&br&是不是觉得一下子效率高了很多,一个空管塔里可以调度的航线可以是前一种方法的几倍到几十倍。
&br&&br&如果你把每一个航线当成一个Sock(I/O 流),
空管当成你的服务端Sock管理代码的话.&br&&br&&b&第一种方法就是最传统的多进程并发模型 (每进来一个新的I/O流会分配一个新的进程管理。)&/b&&br&&b&第二种方法就是I/O多路复用 (单个线程,通过记录跟踪每个I/O流(sock)的状态,来同时管理多个I/O流 。)&/b&&br&&br&&i&其实“I/O多路复用”这个坑爹翻译可能是这个概念在中文里面如此难理解的原因。所谓的I/O多路复用在英文中其实叫 I/O multiplexing. 如果你搜索multiplexing啥意思,基本上都会出这个图:
&br&&/i&&img src=&/5d8e39d83e931da6ba3b6bcc_b.png& data-rawwidth=&250& data-rawheight=&177& class=&content_image& width=&250&&&i&于是大部分人都直接联想到&一根网线,多个sock复用& 这个概念,包括上面的几个回答, 其实不管你用多进程还是I/O多路复用, 网线都只有一根好伐。&b&多个Sock复用一根网线这个功能是在内核+驱动层实现的&/b&。
&br&&/i&&br&&b&重要的事情再说一遍: I/O multiplexing 这里面的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I/O流)的状态(对应空管塔里面的Fight progress strip槽)来同时管理多个I/O流&/b&. 发明它的原因,是尽量多的提高服务器的吞吐能力。&br&&br& 是不是听起来好拗口,看个图就懂了.&br&&br&&img src=&/18d8525aceddb840ea4c_b.jpg& data-rawwidth=&400& data-rawheight=&119& class=&content_image& width=&400&&&br&在同一个线程里面, 通过拨开关的方式,来同时传输多个I/O流, (学过EE的人现在可以站出来义正严辞说这个叫“时分复用”了)。
&br&&br&&i&什么,你还没有搞懂“一个请求到来了,nginx使用epoll接收请求的过程是怎样的”, 多看看这个图就了解了。提醒下,ngnix会有很多链接进来, epoll会把他们都监视起来,然后像拨开关一样,谁有数据就拨向谁,然后调用相应的代码处理。&/i&&br&&br&------------------------------------------&br&了解这个基本的概念以后,其他的就很好解释了。
&br&&br&&b&select, poll, epoll 都是I/O多路复用的具体的实现,之所以有这三个鬼存在,其实是他们出现是有先后顺序的。
&/b&&br&&br&I/O多路复用这个概念被提出来以后, select是第一个实现 (1983 左右在BSD里面实现的)。
&br&&br&select 被实现以后,很快就暴露出了很多问题。
&br&&ul&&li&select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。&br&&/li&&li& select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍,这个无谓的开销就颇有海天盛筵的豪气了。&br&&/li&&li&select 只能监视1024个链接, 这个跟草榴没啥关系哦,linux 定义在头文件中的,参见&em&FD_SETSIZE。&/em&&/li&&li&select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现,尼玛,这个sock不用,要收回。对不起,这个select 不支持的,如果你丧心病狂的竟然关掉这个sock, select的标准行为是。。呃。。不可预测的, 这个可是写在文档中的哦.&/li&&/ul&
“If a file descriptor being monitored by select() is closed in another thread, the result is
unspecified”&br&
霸不霸气&br&&br&于是14年以后(1997年)一帮人又实现了poll,
poll 修复了select的很多问题,比如
&br&&ul&&li&poll 去掉了1024个链接的限制,于是要多少链接呢, 主人你开心就好。&br&&/li&&li& poll 从设计上来说,不再修改传入数组,不过这个要看你的平台了,所以行走江湖,还是小心为妙。&/li&&/ul&&b&其实拖14年那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求。
&/b&&br&&br&但是poll仍然不是线程安全的, 这就意味着,不管服务器有多强悍,你也只能在一个线程里面处理一组I/O流。你当然可以那多进程来配合了,不过然后你就有了多进程的各种问题。&br&&br&于是5年以后, 在2002, 大神 Davide Libenzi 实现了epoll.
&br&&br&epoll 可以说是I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:
&br&&ul&&li&epoll 现在是线程安全的。 &br&&/li&&li&epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。 &br&&/li&&/ul&&br&epoll 当年的patch,现在还在,下面链接可以看得到:&br&&a href=&///?target=http%3A//www.xmailserver.org/linux-patches/nio-improve.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&/dev/epoll Home Page&i class=&icon-external&&&/i&&/a&&br&&br&贴一张霸气的图,看看当年神一样的性能(测试代码都是死链了, 如果有人可以刨坟找出来,可以研究下细节怎么测的).
&br&&img src=&/5a56c53ed22a3f6dfeab4_b.png& data-rawwidth=&640& data-rawheight=&480& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&/5a56c53ed22a3f6dfeab4_r.png&&&br&&i&横轴Dead connections 就是链接数的意思,叫这个名字只是它的测试工具叫deadcon. 纵轴是每秒处理请求的数量,你可以看到,epoll每秒处理请求的数量基本不会随着链接变多而下降的。poll 和/dev/poll 就很惨了。&/i&&br&&br&可是epoll 有个致命的缺点。。只有linux支持。比如BSD上面对应的实现是kqueue。
&br&&br&&i&其实有些国内知名厂商把epoll从安卓里面裁掉这种脑残的事情我会主动告诉你嘛。什么,你说没人用安卓做服务器,尼玛你是看不起p2p软件了啦。 &/i&&br&&br&而ngnix 的设计原则里面, 它会使用目标平台上面最高效的I/O多路复用模型咯,所以才会有这个设置。一般情况下,如果可能的话,尽量都用epoll/kqueue吧。&br&&br&详细的在这里:&br&&a href=&///?target=http%3A//nginx.org/en/docs/events.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Connection processing methods&i class=&icon-external&&&/i&&/a&&br&&br&&i&PS: 上面所有这些比较分析,都建立在大并发下面,如果你的并发数太少,用哪个,其实都没有区别。 如果像是在欧朋数据中心里面的转码服务器那种动不动就是几万几十万的并发,不用epoll我可以直接去撞墙了&/i&。
这个还是很好说清楚的。
假设你是一个机场的空管, 你需要管理到你机场的所有的航线, 包括进港,出港, 有些航班需要放到停机坪等待,有些航班需要去登机口接乘客。
你会怎么做?
最简单的做法,就是你去招一大批空管员,然后每人盯一架飞机, 从进港,接客…
&ul&&li&&b&多线程模型适用于处理短连接,且连接的打开关闭非常频繁的情形,但不适合处理长连接。&/b&多线程模型默认情况下,(在Linux)每个线程会开8M的栈空间,再TCP长连接的情况下,2000/分钟的请求,几乎可以假定有上万甚至十几万的并发连接,假定有10000个连接,开这么多个线程需要G的内存空间!即使调整每个线程的栈空间,也很难满足更多的需求。甚至攻击者可以利用这一点发动DDoS,只要一个连接连上服务器什么也不做,就能吃掉服务器几M的内存,&b&这不同于多进程模型,线程间内存无法共享,因为所有线程处在同一个地址空间中。内存是多线程模型的软肋。&/b&&/li&&li&&b&在UNIX平台下多进程模型擅长处理并发长连接,但却不适用于连接频繁产生和关闭的情形。&/b&Windows平台忽略此项。 同样的连接需要的内存数量并不比多线程模型少,但是得益于操作系统虚拟内存的Copy on
Write机制,fork产生的进程和父进程共享了很大一部分物理内存。但是多进程模型在执行效率上太低,接受一个连接需要几百个时钟周期,产生一个进程 可能消耗几万个CPU时钟周期,两者的开销不成比例。而且由于每个进程的地址空间是独立的,如果需要进行进程间通信的话,只能使用IPC进行进程间通 信,而不能直接对内存进行访问。&b&在CPU能力不足的情况下同样容易遭受DDos,攻击者只需要连上服务器,然后立刻关闭连接,服务端则需要打开一个进程再关闭。&/b&&/li&&/ul&&ul&&li&&b&同时需要保持很多的长连接,而且连接的开关很频繁,最高效的模型是非阻塞、异步IO模型。&/b&而且不要用select/poll,这两个API的有着O(N)的时间复杂度。在Linux用epoll,BSD用kqueue,Windows用IOCP,或者用libevent封装的统一接口(对于不同平台libevent实现时采用各个平台特有的API),这些平台特有的API时间复杂度为O(1)。&b&
然而在非阻塞,异步I/O模型下的编程是非常痛苦的。&/b&由于I/O操作不再阻塞,报文的解析需要小心翼翼,并且需要亲自管理维护每个链接的状态。并且为了充分利用CPU,还应结合线程池,避免在轮询线程中处理业务逻辑。&br&
但这种模型的效率是极高的。以知名的http服务器nginx为例,可以轻松应付上千万的空连接+少量活动链接,每个连接连接仅需要几K的内核缓冲区,想要应付更多的空连接,只需简单的增加内存(数据来源为淘宝一位工程师的一次技术讲座,并未实测)。&b&这使得DDoS攻击者的成本大大增加,这种模型攻击者只能将服务器的带宽全部占用,才能达到目的,而两方的投入是不成比例的。&br&&/b&&/li&&/ul&
多线程模型适用于处理短连接,且连接的打开关闭非常频繁的情形,但不适合处理长连接。多线程模型默认情况下,(在Linux)每个线程会开8M的栈空间,再TCP长连接的情况下,2000/分钟的请求,几乎可以假定有上万甚至十几万的并发连接,假定有10000个连接,开这…
一个成熟的高性能服务器,epoll相关的代码,不到万分之一。&br&而往往入门服务端的人,都天真的人为:高性能服务端开发 == EPOLL,真好笑,&br&之所以会出现 epoll这种被捧上天的垃圾,&br&明明就是 posix 或者最早版本的 unix/bsd/systemv 的设计考虑不完善。&br&&br&按今天的眼光反思 posix 和 unix/bsd/systemv 当年的设计,epoll 这种补丁就不应该实现。&br&异步 reactor 框架应该就只有一个简单而统一的 selector 就足够了,所有系统都相同,提供:&br&&ul&&li&register: 注册&br&&/li&&li&unregister:删除&br&&/li&&li&set:设置&br&&/li&&li&wait:等待事件&br&&/li&&li&read:读取事件&br&&/li&&li&wake:将等待中的 wait 无条件唤醒&/li&&/ul&别以为这些 poll / epoll / kevent / pollset / devpoll / select / rtsig&br&是些什么 “高性能服务器” 的 “关键技术”,它们只是一个 API,而且是对原有系统 API设计不完善打的补丁,各个内核实现了一套自己的补丁方式,它们的存在,见证了服务端技术碎片化的遗憾结果。&br&&br&之所以会有这些乱七八糟的东西,就是早期的 posix / unix/ bsd /systemv 设计不周全,或者不作为留下的恶果。并非什么 “关键技术”。&br&---------&br&不用提 windows 的 iocp了,proactor 会来强奸你代码结构,遭到大家唾弃是有原因的。不像 reactor那样优雅,所以 java nio 选择 reactor 是正确的。即便在 reactor 中,epoll 也是一个失败的例子,调用最频繁的 epoll_ctl 的系统占用估计大家都感受过吧,这方面 epoll 真该象 kevent / pollset 学习一下。
一个成熟的高性能服务器,epoll相关的代码,不到万分之一。而往往入门服务端的人,都天真的人为:高性能服务端开发 == EPOLL,真好笑,之所以会出现 epoll这种被捧上天的垃圾,明明就是 posix 或者最早版本的 unix/bsd/systemv 的设计考虑不完善。按今天的…
&b&收到了在知乎的第一个赞.&/b&&br&&b&网卡设备对应一个中断号, 当网卡收到网络端的消息的时候会向CPU发起中断请求, 然后CPU处理该请求. 通过驱动程序 进而操作系统得到通知, 系统然后通知epoll, epoll通知用户代码. 大致流程是这样.&/b&&br&&b&-----------&/b&&br&&b&在内核的最底层是中断 类似系统回调的机制 不是轮询, 在这个基础上再去看 &a class=&member_mention& data-hash=&bcfe3ed9fe4c3& href=&///people/bcfe3ed9fe4c3& data-tip=&p$b$bcfe3ed9fe4c3&&@蓝形参&/a& 的回答.&/b&
收到了在知乎的第一个赞.网卡设备对应一个中断号, 当网卡收到网络端的消息的时候会向CPU发起中断请求, 然后CPU处理该请求. 通过驱动程序 进而操作系统得到通知, 系统然后通知epoll, epoll通知用户代码. 大致流程是这样.-----------在内核的最底层是中断 类… 的回答.
我是来指出排名第一的回答的BUG的:&br&&&br&&ul&&li&&b&多线程模型适用于处理短连接,且连接的打开关闭非常频繁的情形,但不适合处理长连接。&/b&多线程模型默认情况下,(在Linux)每个线程会开8M的栈空间,再TCP长连接的情况下,2000/分钟的请求,几乎可以假定有上万甚至十几万的并发连接,假定有10000个连接,开这么多个线程需要G的内存空间!即使调整每个线程的栈空间,也很难满足更多的需求。甚至攻击者可以利用这一点发动DDoS,只要一个连接连上服务器什么也不做,就能吃掉服务器几M的内存,&b&这不同于多进程模型,线程间内存无法共享,因为所有线程处在同一个地址空间中。内存是多线程模型的软肋。&/b&&/li&&li&&b&&&/b&&/li&&/ul&你这个说的是Per-connection-Per-thread的方式,不要以此来说这是多线程的模式.&br&&br&多线程的模式有很多的,leader-follow还有Half-Sync/Half-Async.&br&&br&现在这个年代,以我见过的代码,真没见过几个是一个连接上来就一个线程的了.
我是来指出排名第一的回答的BUG的:"多线程模型适用于处理短连接,且连接的打开关闭非常频繁的情形,但不适合处理长连接。多线程模型默认情况下,(在Linux)每个线程会开8M的栈空间,再TCP长连接的情况下,2000/分钟的请求,几乎可以假定有上万甚至十几万的…
闲答。&br&&br&按重要性和基础程度,打乱下顺序吧~~&br&先回问题,然后说怎么实现。&br&&br&注:以下数据,均基于亚马逊AWS c3.xlarge 机型。&br&虚拟CPU:4&br&内存:7.5 GB&br&c3.xlarge 配置和价格: &a href=&///?target=http%3A///cn/ec2/pricing/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AWS | Amazon EC2&i class=&icon-external&&&/i&&/a&&br&&br&~~~~~~~~~~ 分割线 ~~~~~~~~~~&br&&blockquote&2、查了很多资料,单单多进程是不现实的,但是多线程开发linux系统在线程的数量上是有上限的,如何解决?&br&&/blockquote&多线程数量限制?有!但这重要吗?想着开着无限的线程,每个线程都在跑?&br&&br&线程调度是有系统资源开销的!!!!&br&线程调度是有系统资源开销的!!!!&br&线程调度是有系统资源开销的!!!!&br&&br&重要的事情说三遍~~!!!&br&理论上,如果没有I/O等待等让CPU idle的事情,线程数最好等于CPU核心数目。&br&线程数最好等于CPU核心数目!!!&br&线程数最好等于CPU核心数目!!!&br&线程数最好等于CPU核心数目!!!&br&&br&重要的事情再说三遍。&br&&br&举个实际的栗子:&br&本人负责公司网络框架的架构设计和开发。在4核的 c3.xlarge 虚拟机上,压测框架时,2000 worker 线程的QPS(这里理解为TPS问题也不大)远远低于4个worker线程的QPS!&br&4个worker线程,再加其他辅助线程,QPS最大每秒35万。2000 worker线程,加同样的辅助线程,QPS最大每秒15万~~!&br&&br&大部分的时间和其他系统资源都消耗在了线程的切换上~~!这就是为什么单线程的程序在某些情况下比多线程的程序要快~~!(单线程模拟多线程的线程库,不妨参考 state thread:&a href=&///?target=http%3A//state-threads.sourceforge.net& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&state-threads.sourceforge.net&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&。足够简单。) &br&&br&&blockquote&1、本系统处理的业务为多客户端接入,一旦接入基本超过8个小时的长连接,但是登陆以后客户端基本不怎么活动,只有客户端触发相关设定事件才会产生活跃通信。&br&6、系统开发使用TCP协议。&/blockquote&目前开的框架,TCP长链接,最大压测链接108万,总 QPS 6万。&br&服务器也是c3.xlarge。开了18台机器做客户端压,每台机器6万链接(记得要修改 /proc/sys/net/ipv4/ip_local_port_range,不然一台机器出不了这么多链接)。&br&&br&&blockquote&4、客户端接入时间随机,系统运行初期不会存在同一时间成千上万用户登陆的情况,但是用户一旦接入服务器就会长时间不会断开。&br&&/blockquote&压测,每批次新加6万链接。&br&&br&&blockquote&5、能否使用epoll技术跟多线程技术配合开发?HOW?&br&&/blockquote&Linux上,必须的!!!&br&&br&&blockquote&3、QQ、SKYPE等的多客户端登陆软件,服务器一般是如何设计开发的?&br&&/blockquote&没有一般,都是根据具体需求实际定制!!!&br&没有一般,都是根据具体需求实际定制!!!&br&没有一般,都是根据具体需求实际定制!!!&br&&br&但分布式、去中心化、无状态化、一致性哈希、……,都是必须的~~~!&br&但分布式、去中心化、无状态化、一致性哈希、……,都是必须的~~~!&br&但分布式、去中心化、无状态化、一致性哈希、……,都是必须的~~~!&br&&br&(说了好几个三遍,累死了~~~喝点水~~)&br&不妨上网搜搜,将网络架构,服务器架构的演化。中小型公司按那些套路来,基本都能搞定。图太多,说起来太罗嗦。都是重复劳动,我就不写了,请自己搜。&br&&br&你问大公司?请参考第一句话:没有一般,都是根据具体需求实际定制!!!&br&补充一点:网上搜的,都会对memcached、redis 等有很大的依赖。但我上家公司,也用,很重要,但只是给PHP端用。后端服务集群全用专用缓存~!!&br&专用缓存~~!含逻辑~~!自己开发~~!&br&专用缓存~~!含逻辑~~!自己开发~~!&br&专用缓存~~!含逻辑~~!自己开发~~!&br&又说三遍。&br&撑住1.6亿注册用户。&br&&br&&blockquote&7、希望大家能给个详细开发框架。&br&&/blockquote&框架?是参考架构设计吧?&br&&br&框架有很多,比如ICE,Facebook Thrift、Apache Thrift 等~~&br&注意,后两个thrift不完全相同~~!!!&br&&br&然后怎么做:&br&没有固定的套路!!!只有要注意的要点!!!&br&没有固定的套路!!!只有要注意的要点!!!&br&没有固定的套路!!!只有要注意的要点!!!&br&&br&又是三遍。&br&&br&要点:&br&尽量少的线程切换&br&尽量少的共享冲突&br&尽量无锁&br&工作中的线程数尽量等于CPU核心数&br&尽量没有等待时间片的线程&br&逻辑尽量简化,避免不必要的封装和转发&br&工程不是学术,OOP要给简单易用高性能好维护让道~~!&br&(就是ICE概念多,太复杂,上家公司才开发了自己的框架。就是因为Facebook Thrift 太罗嗦,一个异步都要绕好几道弯,链接还和CPU核心绑定(如果就一个链接,10万QPS,你会发现就一个核忙得要死,其他核心都在吃干饭~),现在公司才决定自己开发网络框架。)&br&EPOLL:Edge Trigger、OneShot!!!&br&不要在epoll_wait()线程中用太长时间处理非epoll_wait()的事情。&br&能用atomic的就不要用mutex(这是C++11的事了)&br&&br&上面就不三遍了,太多太啰嗦~~~&br&&br&~~~~~~~~~~~~~ 7月3日追加 ~~~~~~~~~~~~~~&br&&br&感谢 &a href=&/people/irons.du& class=&internal&&Irons Du&/a&&a href=&/people/ceng-ling-heng-94& class=&internal&&曾凌恒&/a&&a href=&/people/gao-ying-da& class=&internal&&每天不吃冰淇淋&/a& 的评论,涉及到一些昨天忘了的事情。&br&PS:今次不三遍了~~:)&br&&br&追加要点:&br&&br&A. 数据库&br&数据库一定要分布。&br&如果有好的数据路由中间层服务,或者好的集群管理器,或者好的Sharding服务,是MongoDB还是MySQL完全不重要~~~!&br&如果使用MongoDB,需要注意查询 API 的where处理(自定义Javascript查询条件)。C API的 where 处理非常非常低效(至少去年还是这样。今年因项目的关系,没有跟进)&br&数据库一定要分库分表。&br&分表强烈建议使用Hash分表,尽量避免按区段分表。&br&Hash分表设计好了,要扩容也非常容易。区段分表貌似扩容很容易,但时间长了,你的热点数据分布就极其不均匀了。&br&分库分表,尽量异步并发查询(靠你的数据路由中间件了。当前两家公司都是自己开发的,外面的吹得太凶,不实用。)&br&除支付业务外,严格禁止联合查询、复合查询、事务操作!!!!&br&(支付一会单说)&br&分库分表,表都不在一台机器上了,联合查询、复合查询、事务操作必然失败~~~!&br&联合查询、复合查询、事务相关的操作请由中间层服务配合完成!!!&br&PS:MySQL等,联合查询、复合查询、事务操作,效率极低~~极低~~~!!!还会因为锁表时间过长而阻塞其他查询~~!!&br&支付的逻辑设计并分解好了,可以不用事务完成。要用事务,请确定相关的表均部署在同一台数据库实例上!!!&br&&br&B. 去中心化、无状态:&br&高弹性架构这是必须的。&br&尽量将状态剥离成一个单独的状态服务(也是分布式集群),其他业务逻辑全部变成无状态的。状态信息请通过状态服务处理。&br&无状态的一般去中心化都很简单。无外乎一致性哈希、随机、轮转等等。&br&去中心化也是确保无单点故障!!!&br&如果非要有中心,请尽量选折以下两个方案:&br&1. 中心如果很简单,请确保在崩溃/杀死后,1秒钟内能立刻复活启动~~~&br&2. 中心改为管理集群,自动选举主核心。当原主核心失效后,新的核心自动接管当前集群网络。&br&&br&C. 协议:&br&不要使用XML!!!这都不想说了,让我看到就是千万只神兽在胸中蹦腾~~!!!&br&如果文本,请使用JSON。如果二进制,请使用JSON对应的二进制化协议。BSON嘛~~持保留态度。&br&如果需要协议的灰度升级,如果在协议灰度升级时不想实现两套不同版本的接口,请远离使用IDL的协议/框架!!!&br&这也是这两家公司弃用 ICE和Facebook Thrift 的原因之一。&br&&br&最后,如果你是初入行者,不妨多看看前面其他大拿推荐的资料,看看Reactor模式,看看Proactor。多看看其他框架,服务集群的设计。&br&但是!!&br&当你成长后,一切模式、设计,都是扯淡~~!&br&模式是死的,需求是变动的,人和思维是活的!&br&只有根据实际需求,具体设计!具体定制!!!&br&(还记得无字真经吗~~)&br&&br&(貌似扯远了,已经不是服务器高并发了,而是高并发后端系统集群了。。。-_-! )
闲答。按重要性和基础程度,打乱下顺序吧~~先回问题,然后说怎么实现。注:以下数据,均基于亚马逊AWS c3.xlarge 机型。虚拟CPU:4内存:7.5 GBc3.xlarge 配置和价格: ~~~~~~~~~~ 分割线 ~~~~~~~~~~2、查了很多资料…
按照题主的意思 是根据内存去算一个最大并发的连接数. 那么首先要找出来单个连接消耗内存的地方.&br&第一个首先是socket buffer.
read 和write 分别有一个, 默认大小在&br&&ul&&li&/proc/sys/net/ipv4/tcp_rmem (for read)&/li&&li&/proc/sys/net/ipv4/tcp_wmem (for write)&/li&&/ul&默认大小都是87K和16K, 最低是4K和4K, 最高是2M,2M, 实际使用默认值最低也要保留8K,8K.&br&&br&然后是逻辑IO缓冲区&br&
就是比如你监听了recv事件 事件来了 你要有内存可用(一般都是socket建立起就分配好,断开才会释放的).&br&这个内存是自己写socket程序时候自己控制的, 最低也要4K,4K, 实际使用8K,8K至少.&br&&br&现在设定一个优化方案和使用场景,
首先假设4G内存全部为空闲(系统和其他进程也要内存的....&br&&br&假如网络包的大小都可以控制在4K以下, 假设所有连接的网络都不会拥堵, 或者拥堵时候的总量在4K以下:&br&一个连接的内存消耗是4+4+4+4=16K&br&&b&4G/16K=26.2万并发&/b&&br&&br&假如网络包的大小都可以控制在8K以下, 假设所有连接的网络都不会拥堵, 或者拥堵时候的总量在8K以下&br&一个socket的内存占用介于 24K ~ 32K之间, 保守的按照32K算
&br&&b&4G/32K=13.1万并发, 这个在生产环境作为一个纯网络层面的内存消耗, 是可以作为参考的.&/b&&br&&br&假如使用默认配置,
假如所有连接的网络都出现严重拥堵, 不考虑逻辑上的发送队列的占用,&br&使用默认配置是2M+2M+8+8 ~= 4M&br&&b&4G/4M=1024并发 ( ...&/b&&br&&b&如果考虑到发送队列也拥堵的话 自己脑补.&/b&&br&&br&如果只是为了跑分 为了并发而优化, 没有常驻的逻辑缓冲区 并且socket的网络吞吐量很小并且负载平滑,
把socket buffer size设置系统最低.&br&那么是&br&&b&4G/8K = 52.4万并发
这个应该是极限值了.&/b&
按照题主的意思 是根据内存去算一个最大并发的连接数. 那么首先要找出来单个连接消耗内存的地方.第一个首先是socket buffer. read 和write 分别有一个, 默认大小在/proc/sys/net/ipv4/tcp_rmem (for read)/proc/sys/net/ipv4/tcp_wmem (for write)默认大小…
看 Tornado 源码时候,我也产生了同样的疑问。&br&&br&首先基于 Reactor 模型,socket.fd 已经被放到 ioloop(事件循环) 中,通过多路复用监听到 fd 已可用,并开始调用对应的事件处理函数(handle_read/hanle_accept)。&br&&br&&div class=&highlight&&&pre&&code class=&language-python&&&span class=&k&&def&/span& &span class=&nf&&handle_read&/span&&span class=&p&&():&/span&
&span class=&n&&socket&/span&&span class=&o&&.&/span&&span class=&n&&setblocking&/span&&span class=&p&&(&/span&&span class=&bp&&False&/span&&span class=&p&&)&/span&
&span class=&k&&while&/span& &span class=&bp&&True&/span&&span class=&p&&:&/span&
&span class=&k&&try&/span&&span class=&p&&:&/span&
&span class=&n&&data&/span& &span class=&o&&=&/span& &span class=&n&&socket&/span&&span class=&o&&.&/span&&span class=&n&&recv&/span&&span class=&p&&(&/span&&span class=&mi&&1024&/span&&span class=&p&&)&/span&
&span class=&k&&except&/span& &span class=&n&&socket&/span&&span class=&o&&.&/span&&span class=&n&&error&/span&&span class=&p&&,&/span& &span class=&n&&e&/span&&span class=&p&&:&/span&
&span class=&k&&if&/span& &span class=&n&&e&/span&&span class=&o&&.&/span&&span class=&n&&args&/span&&span class=&p&&[&/span&&span class=&mi&&0&/span&&span class=&p&&]&/span& &span class=&ow&&in&/span& &span class=&p&&(&/span&&span class=&n&&errno&/span&&span class=&o&&.&/span&&span class=&n&&EWOULDBLOCK&/span&&span class=&p&&,&/span& &span class=&n&&errno&/span&&span class=&o&&.&/span&&span class=&n&&EAGAIN&/span&&span class=&p&&):&/span&
&span class=&k&&return&/span&
&span class=&k&&raise&/span&
&span class=&k&&def&/span& &span class=&nf&&handle_accept&/span&&span class=&p&&():&/span&
&span class=&n&&socket&/span&&span class=&o&&.&/span&&span class=&n&&setblocking&/span&&span class=&p&&(&/span&&span class=&bp&&False&/span&&span class=&p&&)&/span&
&span class=&k&&while&/span& &span class=&bp&&True&/span&&span class=&p&&:&/span&
&span class=&k&&try&/span&&span class=&p&&:&/span&
&span class=&n&&connection&/span&&span class=&p&&,&/span& &span class=&n&&address&/span& &span class=&o&&=&/span& &span class=&n&&socket&/span&&span class=&o&&.&/span&&span class=&n&&accept&/span&&span class=&p&&()&/span&
&span class=&k&&except&/span& &span class=&n&&socket&/span&&span class=&o&&.&/span&&span class=&n&&error&/span&&span class=&p&&,&/span& &span class=&n&&e&/span&&span class=&p&&:&/span&
&span class=&k&&if&/span& &span class=&n&&e&/span&&span class=&o&&.&/span&&span class=&n&&args&/span&&span class=&p&&[&/span&&span class=&mi&&0&/span&&span class=&p&&]&/span& &span class=&ow&&in&/span& &span class=&p&&(&/span&&span class=&n&&errno&/span&&span class=&o&&.&/span&&span class=&n&&EWOULDBLOCK&/span&&span class=&p&&,&/span& &span class=&n&&errno&/span&&span class=&o&&.&/span&&span class=&n&&EAGAIN&/span&&span class=&p&&):&/span&
&span class=&k&&return&/span&
&span class=&k&&raise&/span&
&/code&&/pre&&/div&&br&假如 socket 的读缓冲区中已有足够多的数据,需要调用三次 read 才能读取完。或 ACCEPT 队列已经有三个「握手已完成的连接」。&br&&br&非阻塞 I/O 的处理方式:循环的 read 或 accept,直到读完所有的数据(抛出 EWOULDBLOCK 异常)。&br&&br&阻塞 I/O 的处理方式:每次只能调用一次 read 或 accept,因为多路复用只会告诉你 fd 对应的 socket 可读了,但不会告诉你有多少的数据可读,所以在 handle_read/handle_accept 中只能 read/accept 一次,你无法知道下一次 read/accept 会不会发生阻塞。所以只能等 ioloop 的第二次循环,ioloop 告诉你 fd 可用后再继续调用 handle_read/handle_accept 处理,然后再循环第三次。&br&&br&所以你会发现,非阻塞 I/O 只需要一次多路复用的调用,而阻塞 I/O 则需要三次。&br&&br&======Update======&br&另外在下面这几种情况,必须采用非阻塞 I/O:&br&&ul&&li&在边缘触发(Edge Trigger)环境,参见 &a href=&/question//answer/& class=&internal&&为什么 IO 多路复用要搭配非阻塞 IO? - 郭春阳的回答&/a&&br&&/li&&li&在多线程环境,参见 &a href=&/question//answer/& class=&internal&&为什么 IO 多路复用要搭配非阻塞 IO? - 林晓峰的回答&/a&&br&&/li&&li&在触发 select bug 时候,参见 &a href=&/question//answer/& class=&internal&&在使用Multiplexed I/O的情况下,还有必要使用Non Blocking I/O么 ? - 知乎用户的回答&/a&&/li&&/ul&
看 Tornado 源码时候,我也产生了同样的疑问。首先基于 Reactor 模型,socket.fd 已经被放到 ioloop(事件循环) 中,通过多路复用监听到 fd 已可用,并开始调用对应的事件处理函数(handle_read/hanle_accept)。def handle_read():
socket.setblocking(F…
man 2 select 「BUGS」节:&br&&br&
Under Linux, select() may report a socket file descriptor as &ready for reading&, while nevertheless a subsequent read blocks.
This could for example happen when&br&
data has arrived but upon examination has wrong checksum and is discarded.
There may be other circumstances in which a file descriptor is spuriously reported
Thus it may be safer to use O_NONBLOCK on sockets that should not block.&br&&br&就算数据不会被别人读走,也可能被内核丢弃。还有文档没有明说的其它情况。
man 2 select 「BUGS」节: Under Linux, select() may report a socket file descriptor as "ready for reading", while nevertheless a subsequent read blocks. This could for example happen when data has arrived but upon examination has wrong ch…
1. 你测一下呗。&br&就算这里有人告诉你哪个效率更高,你怎么判断他是不是在胡说八道?还不是得自己测。&br&&br&如果你不知道怎么测效率,那么问哪个效率更高对你是无意义的。&br&如果你知道怎么测效率,那么问哪个效率更高就属于多此一举。&br&&br&2. 你会因为哪个效率高而更换操作系统吗?
1. 你测一下呗。就算这里有人告诉你哪个效率更高,你怎么判断他是不是在胡说八道?还不是得自己测。如果你不知道怎么测效率,那么问哪个效率更高对你是无意义的。如果你知道怎么测效率,那么问哪个效率更高就属于多此一举。2. 你会因为哪个效率高而更换操作…
io分为网络IO和磁盘IO,对于网络IO,使用epoll之类的就可以了。但是对于瓷盘IO,没有完美的办法,所以都是使用多线程阻塞模拟的,不同在于Windows下的IOCP是在系统内核里提供的线程池,而Linux之类的在用户层提供的线程池。&br&&br&libeio和libev是node较早版本使用的,在libuv提供之后,这两个库均不在使用。
io分为网络IO和磁盘IO,对于网络IO,使用epoll之类的就可以了。但是对于瓷盘IO,没有完美的办法,所以都是使用多线程阻塞模拟的,不同在于Windows下的IOCP是在系统内核里提供的线程池,而Linux之类的在用户层提供的线程池。libeio和libev是node较早版本使用…
就怕这种半截儿问题。&br&&ol&&li&你说的惊群问题到底是什么意思?现象是什么?&/li&&li&有没有简短的示例代码证明这一问题确实存在?复现步骤是什么?&/li&&li&为什么你认为内核里的这一行代码应该能解决这一问题?解决之后应该有什么现象?&/li&&li&作为提问者,你要先回答清楚上面3个问题,之后,才能问“内核源码eventpoll.c中已经有如下代码,那为什么还是会发生惊群?”&/li&&/ol&
就怕这种半截儿问题。你说的惊群问题到底是什么意思?现象是什么?有没有简短的示例代码证明这一问题确实存在?复现步骤是什么?为什么你认为内核里的这一行代码应该能解决这一问题?解决之后应该有什么现象?作为提问者,你要先回答清楚上面3个问题,之后…
简单举个例子(可能也不是很形象)&br&select/poll&br&饭店服务员(内核)告诉饭店老板(用户程序):”现在有客人结账“&br&但是这个服务员没人明确告诉老板,哪几桌的客人结帐。老板得自儿一个一个桌子去问:请问是你要结帐?&br&&br&epoll&br&饭店服务员(内核)告诉饭店老板(用户程序):”1,2,5号客人结账“&br&老板就可以直接去1,2,5号桌收钱了
简单举个例子(可能也不是很形象)select/poll饭店服务员(内核)告诉饭店老板(用户程序):”现在有客人结账“但是这个服务员没人明确告诉老板,哪几桌的客人结帐。老板得自儿一个一个桌子去问:请问是你要结帐?epoll饭店服务员(内核)告诉饭店老板(用…
不是很懂你们城里人为什么要这么玩,为什么用了epoll还要使用多线程?好吧退一步讲,就算新内核不会惊群,那为什么同一个fd要丢到所有线程的epoll实例中去?真的很不理解你们城里人为什么要这么玩,这岂不是自找麻烦?
不是很懂你们城里人为什么要这么玩,为什么用了epoll还要使用多线程?好吧退一步讲,就算新内核不会惊群,那为什么同一个fd要丢到所有线程的epoll实例中去?真的很不理解你们城里人为什么要这么玩,这岂不是自找麻烦?
假设一个这样的场景:&br&你需要将一个10G大小的文件返回给用户,那么你简单send这个文件是不会成功的。&br&这个场景下,你send 10G的数据,send返回值不会是10G,而是大约256k,表示你只成功写入了256k的数据。接着调用send,send就会返回EAGAIN,告诉你socket的缓冲区已经满了,此时无法继续send。&br&此时异步程序的正确处理流程是调用epoll_wait,当socket缓冲区中的数据被对方接收之后,缓冲区就会有空闲空间可以继续接收数据,此时epoll_wait就会返回这个socket的EPOLLOUT事件,获得这个事件时,你就可以继续往socket中写出数据。&br&&br&这里有个简单的例子,里面包含了EPOLLOUT处理的详细过程&br&&a href=&///?target=https%3A///yedf/handy/blob/master/raw-examples/epoll.cc& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&handy/epoll.cc at master · yedf/handy · GitHub&i class=&icon-external&&&/i&&/a&&br&还有epoll的ET模式下的EPOLLOUT处理过程&br&&a href=&///?target=https%3A///yedf/handy/blob/master/raw-examples/epoll-et.cc& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/yedf/handy/b&/span&&span class=&invisible&&lob/master/raw-examples/epoll-et.cc&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&
假设一个这样的场景:你需要将一个10G大小的文件返回给用户,那么你简单send这个文件是不会成功的。这个场景下,你send 10G的数据,send返回值不会是10G,而是大约256k,表示你只成功写入了256k的数据。接着调用send,send就会返回EAGAIN,告诉你socket的缓…
&p&参考:&/p&&div class=&highlight&&&pre&&code class=&language-text&&http://my.oschina.net/lwwklys/blog/73427
&/code&&/pre&&/div&&br&&p&Epoll是Linux内核为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44),在2.6内核中得到广泛应用,例如LightHttpd。&/p&&p&Epoll是&/p&&div class=&highlight&&&pre&&code class=&language-text&&/wiki/Linux%E5%86%85%E6%A0%B8
&/code&&/pre&&/div&&p&为处理大批量句柄而作了改进的&a href=&///?target=http%3A///wiki/poll& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&poll&i class=&icon-external&&&/i&&/a&。要使用epoll只需要这三个系统调用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44),在2.6内核中得到广泛应用,例如&a href=&///?target=http%3A///wiki/LightHttpd& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&LightHttpd&i class=&icon-external&&&/i&&/a&。&/p&&br&epoll - epoll的优点&br&&ul&&li&支持一个进程打开大数目的socket&div class=&highlight&&&pre&&code class=&language-text&&/wiki/socket
&/code&&/pre&&/div&描述符(FD)&/li&&/ul&&p&select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。&/p&&ul&&li&IO效率不随FD数目增加而线性下降&/li&&/ul&&p&传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是&活跃&的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对&活跃&的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有&活跃&的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个&伪&AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟&a href=&///?target=http%3A///wiki/WAN& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&WAN&i class=&icon-external&&&/i&&/a&环境,epoll的效率就远在select/poll之上了。&/p&&ul&&li&使用&a href=&///?target=http%3A///wiki/mmap& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&mmap&i class=&icon-external&&&/i&&/a&加速内核与用户空间的消息传递。&/li&&/ul&&p&这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。&/p&&ul&&li&内核微调&/li&&/ul&&p&这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核&a href=&///?target=http%3A///wiki/TCP/IP& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TCP/IP&i class=&icon-external&&&/i&&/a&协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX&/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的&a href=&///?target=http%3A///wiki/epoll& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&NAPI&i class=&icon-external&&&/i&&/a&网卡驱动架构。&/p&&br&epoll - epoll的使用&br&&p&令人高兴的是,2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多,所以,大部分情况下,强大的东西往往是简单的。唯一有点麻烦是epoll有2种工作方式:LT和ET。&/p&&p&LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.&/p&&p&ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。&/p&&p&epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用,&br&具体用法请参考&/p&&div class=&highlight&&&pre&&code class=&language-text&&http://www.xmailserver.org/linux-patches/nio-improve.html
&/code&&/pre&&/div&&p&&br&在&/p&&div class=&highlight&&&pre&&code class=&language-text&&/rn/
&/code&&/pre&&/div&&p&有一个完整的例子&/p&&br&epoll - 参考资料&br&&br&&a href=&///?target=http%3A///5351040.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Linux 2.6内核中提高网络I/O性能的新方法-epoll&i class=&icon-external&&&/i&&/a&&br&&p&&a href=&///?target=http%3A//lse.sourceforge.net/epoll/index.html& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&lse.sourceforge.net/epo&/span&&span class=&invisible&&ll/index.html&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&。&/p&
参考:http://my.oschina.net/lwwklys/blog/73427
Epoll是Linux内核为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。它是在2.5.44内核中被引进的(epoll(4) is a new API introduced …
使用ET模式,可以便捷的处理EPOLLOUT事件,省去打开与关闭EPOLLOUT的epoll_ctl(EPOLL_CTL_MOD)调用。从而有可能让你的性能得到一定的提升。&br&
例如你需要写出1M的数据,写出到socket 256k时,返回了EAGAIN,ET模式下,当再次返回EPOLLOUT时,继续写出待写出的数据,当没有数据需要写出时,不处理直接略过即可。而LT模式则需要先打开EPOLLOUT,当没有数据需要写出时,再关闭EPOLLOUT(否则会一直会返回EPOLLOUT事件)&br&
当nginx处理大并发大流量的请求时,LT模式会出现较多的epoll_ctl调用用于开关EPOLLOUT,因此ET模式就更合适了&br&关于某些场景下ET模式比LT模式效率更好,我有篇文章进行了详细的解释与测试,参看&br&&a href=&///?target=http%3A//blog.csdn.net/dongfuye/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&epoll LT/ET 深入剖析&i class=&icon-external&&&/i&&/a&
使用ET模式,可以便捷的处理EPOLLOUT事件,省去打开与关闭EPOLLOUT的epoll_ctl(EPOLL_CTL_MOD)调用。从而有可能让你的性能得到一定的提升。 例如你需要写出1M的数据,写出到socket 256k时,返回了EAGAIN,ET模式下,当再次返回EPOLLOUT时,继续写出待写出…
没有做过实际测试,从文档的说明上来看,ET的效率更高,原因在于epoll的工作原理是为socket号设置了一个微型的callback。在ET模式下,socket从非就绪到就绪的状态只会触发一次,将事件放入epoll,然后callback被移除,到socket再次变回非就绪状态前不需要做其他工作了;而LT需要维护一个触发列表,在每次epoll的时候都重新把之前触发了而未移除的列表重新加到epoll的结果里。&br&从应用的角度上,ET最大的好处是不需要经常调用modify方法修改等待的事件,直接将EPOLLIN和EPOLLOUT一次加入就可以,当写满缓冲区时会自动产生EPOLLOUT,而在下次写满之前不会再出现EPOLLOUT。这样就省去了两次modify的时间。
没有做过实际测试,从文档的说明上来看,ET的效率更高,原因在于epoll的工作原理是为socket号设置了一个微型的callback。在ET模式下,socket从非就绪到就绪的状态只会触发一次,将事件放入epoll,然后callback被移除,到socket再次变回非就绪状态前不需要做…
select返回能读,但没返回能读多少,比如可以读20字节,你读两次每次10以内没问题,但第一次就读完二十后面就就该eagain了,写也一样
select返回能读,但没返回能读多少,比如可以读20字节,你读两次每次10以内没问题,但第一次就读完二十后面就就该eagain了,写也一样
已有帐号?
无法登录?
社交帐号登录

我要回帖

更多关于 epoll 并发量 的文章

 

随机推荐