有段时间为公司做了一些技术收集方面的工作,ice作为一个开源的网络通讯中间件,肯定是我们不错的研究对象。
对ice3.3.1框架开发的服务器做了一个简单的压力测试,数据见下:
目的:测试在大量并发连接下服务器响应客户端的时间
描述:统计的数据直都使用客户端从发出消息到返回消息为依据,每一组实验分别测试3次,然后取其平均值。测试网络环境为内网
客户端不断的发送数据并等待服务端返回,服务端不做任何业务处理,也不做任何与磁盘有关的io处理,而是直接返回或返回同样大小的数据。
(windows服务系统:Intel(R) Xeon(R) E5520 2.27GHz 2.26GHz, RAM 2G)
(单位:ms)
(测试数据与机器性能有关)
(windows服务下超过1000个连接以上效率基本差到无法测试的程度)
1、windows服务器只收数据,不返回给客户端数据只做应答的情况:
连接条数/ 发送数据大小 |
服务器工作线程数目1,平均响应时间 |
服务器工作线程数目8,平均响应时间 |
服务器工作线程数目12,平均响应时间 |
100(1个机器) 1024字节 |
31.74 |
32.38 |
35.80 |
800(2个机器) 512字节 |
254.46 |
177.85 |
132.25 |
|
|
|
|
|
|
|
|
2、windows服务器既接收数据,同时又返回同样大小的数据的情况:
连接条数/ 发送数据 |
服务器工作线程数目1,平均响应时间 |
服务器工作线程数目8,平均响应时间 |
服务器工作线程数目12,平均响应时间 |
100(1个机器) 1024字节 |
29.38 |
31.26 |
32.66 |
800(2个机器) 512字节 |
237.91 |
209.94 |
169.04 |
|
|
|
|
|
|
|
|
(Linux服务系统:Allowing 4 CPUs Intel(R) Xeon(R) CPU X3320 @ 2.50GHz)
(单位:ms)
(测试数据与机器性能有关)
3、服务器只做应答的情况:
并发连接条数/ 发送数据 |
服务器工作线程数目1,平均响应时间 |
服务器工作线程数目4,平均响应时间 |
服务器工作线程数目8,平均响应时间 |
服务器工作线程数目12,平均响应时间 |
100(1个机器) 1024字节 |
21.65 |
28.71 |
19.48 |
23.84 |
800(2个机器) 512字节 |
20.49 |
19.83 |
23.04 |
27.52 |
1200(3个机器) 256字节 |
24.03 |
23.82 |
25.79 |
24.59 |
2000(4个机器) 128字节 |
25.01 |
25.09 |
25.33 |
24.75 |
3000(5个机器) 64字节 |
33.90 |
34.09 |
32.20 |
31.17 |
4、服务器返回接收到的数据的情况:
并发连接条数/ 发送数据 |
服务器工作线程数目1,平均响应时间 |
服务器工作线程数目4,平均响应时间 |
服务器工作线程数目8,平均响应时间 |
服务器工作线程数目12,平均响应时间 |
100(1个机器) 1024字节 |
20.00 |
21.98 |
26.00 |
20.10 |
800(2个机器) 512字节 |
18.12 |
18.07 |
25.65 |
19.95 |
1200(3个机器) 256字节 |
26.41 |
31.65 |
34.12 |
37.15 |
2000(4个机器) 128字节 |
31.35 |
38.59 |
30.86 |
35.79 |
3000(5个机器) 64字节 |
34.90 |
39.11 |
36.48 |
39.74 |
总结: 由于测试所用的客户端机器都是在开发人员的电脑上工作时间测试所得, 所以数据不是非常精确,但也能看出个大概。首先ice3.3.3性能方面在linux下的表现比windows优越太多,主要是因为在linux使用了epoll模型,而在windows仅采用了的select 模型,而没有采用iocp模型。连接数据多时,效率下降很厉害,并发达到1000时测试效果非常差,数据就不列出了。其主要原因是select模型的协议栈的状态检查需要用户去查询,而其他模型由内核通知,不需要用户去轮循检查。其次linux下,在多cpu处理器情况下,服务器工作线程个数的多少跟只有1个时差别不大,说明3000个并发处理肯定没达到1个cpu处理的极限直,可以再增加并发连接数量。具体最高能到多少,这个当时没有再进一步测试。3000个并发时,按照每处理一次响应40ms计算,(10000/40)*3000 ,大概1秒钟处理75000次响应,最高1秒钟能处理多少次响应,这个可以提高或减少并发连接数目进一步测试。总会在一定并发连接的情况下1秒钟能处理的响应次数最多。 跟从事过游戏服务器开发的同行交流过,大概好点的io模型在不太差的机器上在不考虑业务以及磁盘io以及收发数据大小的情况下每秒处理100000次响应是可以的。所以使用epoll做服务器时,io模型的性能可以不用怎么考虑,影响性能的关键是 io处理以及业务逻辑处理。
说到每秒响应次数不能不说到每秒收发数据量,但这个跟每次收发数据量的大小有关。从上表可以算出epoll2000个并发时每秒收发数据量大概是 ((1000/35)*2000*128)/(1024*1024)=6.97 大概7M每秒3000个并发时每秒收发数据量大概是 ((1000/40)*3000*64)/(1024*1024)=4.57M每秒, 所以每秒钟收发数据量大小并不能表明io模型效率高低,因为这跟每次发送的数据量大小有关系。
说到这里要说一下epoll比select好的一些优点(以下的几点比较是来自互联网上一些朋友发表的,我这里引用一下):
epoll的优点:
1.支持一个进程打开大数目的socket描述符(FD)
select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
2.IO效率不随FD数目增加而线性下降
传统的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模拟WAN环境,epoll的效率就远在select/poll之上了。
3.使用mmap加速内核与用户空间的消息传递。
这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。
4.内核微调
这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。
下面同时也说一下epoll与iocp的比较(同样以下的几点比较是来自互联网上一些朋友发表的,我这里引用一下):
首先,我们看一下它们相同的地方。
两者都是处理异步IO的高效模型,这种高效,除了“异步处理”这个共同的特征之外,二者都可以通过指针携带应用层数据:在IOCP里,应用层数据可以通过单句柄数据和单IO数据来与IOCP底层通信;而在epoll里,可以通过epoll_data里的"void *ptr"来传递。这是一种很重要的思想,也是它们高效的原因所在:当事件的通知到来时,它不仅告诉你发生了什么样的事件,还同时告诉这次事件所操作的数据是哪些。
那么,epoll和iocp到底又有什么不同呢?
EPOLL是半成品,IOCP是成品,底层机制一样,协议栈的状态检查不需要用户去查询,由作业系统来通知。其实这是任何守护性逻辑高性能的基础机制。
但是EPOLL只是告诉你现在可以读和写,即协议栈的读写缓冲被初始化或重设(对于写,上次数据已经提交并写缓冲重设为空,对于读,栈议栈读缓冲已经开始接受数据。)
但是写和读的过程还是由用户来控制,系统只是告诉你已经为你准备好了和网络驱动对接好的读写的通道。如果某个通道的读写很慢,我们其实自己可以控制,比如要读8K字节,但经过x秒只读到几个字节,这说明这个通道很差,我们可以将这个IO通道从EPOLL中分离出来把它投递到一个阻塞的socket中,而不影响整个EPOLL的性能。所以EPOLL虽然是半成品,但用户有更高的控制权。
而对于IOCP,从名称就可以知道,系统不仅控制IO通道的状态,而且把读写操作都做完了才通知用户。对于读,系统已经把数据读好放在buffer中,其实相当于是RBF,对于写,已经是写出了n长度的字节,所以即使某个IO通道上的传输很慢,你也无法控制,因为当你收到通知时,系统已经是读好数据或写出数据。所以IOCP在用户的控制上没有灵活的空间。
但是由系统来做毕竟比普通的二流以下的程序员自己来控制性能普遍来说要更好一些。
在高性能服务器的开发中,采用一个非阻塞的IO模型配两三个阻塞的socket混合处理,是最合理的,因为在大量连接中总会有一些客户端传输很慢,对于非常慢的连接,EPOLL,IOCP还不如阻塞模型处理性能更好,即时读写速度是一样的,但阻塞模型简单,上下文切换和内存分配的开销比较少。所以把一些很慢的连接重新投递到阻塞的socket上而让EPOLL能有更多的机会去处理传输非常快的连接才是非阻塞的优势。相比来说,IOCP就不能这样做。