讨论:如果服务器需要承受1000个以上的并发,你同意使用线程阻塞式还是非阻塞式?(300分)

前两天看到的文章,也许有点用,这是第一篇,后面又有为FreeBSD正名的的一篇,
不过和本主题没什么关系。


哪种OS更适合高性能网络应用
摘要
本文通过实验测试对Linux、Solaris (for Intel)、FreeBSD和Windows 2000在运行高性能
网络应用程序方面的速度进行了比较。描述了如何根据需要从您的软件提供商那里获得相
应性能和软件体系结构的产品,解释了每个设计是如何产生不同的性能特性的,并且最终
确定了对于每个通用的网络程序设计来说哪个操作系统最为合适。我们通过仿真和真实环
境测试提出了操作系统基准,并对相应结果给出了评价。
我们发现,与其所依赖的操作系统相比较,软件应用程序的体系结构是决定其执行速度的
更主要因素。我们的基准程序(benchmarks)证明,基于进程的体系结构与异步任务体系
结构之间会有12倍的性能差异。值得注意的是,即使使用最有效的异步体系结构,不同操
作系统间的性能差异也会相差75%以上。疑惧我们的度量基准,我们发现Linux是执行性能
最好的操作系统,它比排在第二位的Solaris的性能高35%,然后才是Windows,排在最后的
是FreeBSD。

背景

在Lyris技术公司,我们的任务是编写高性能、跨平台、基于email的应用程序。众所周知,
更好的应用程序性能是决定竞争优势的关键因素,所以我们花了大量的时间在影响应用程
序性能的所有因素(软件、硬件和操作系统)方面进行调整。我们的客户经常问的一个问
题是哪个操作系统最适合运行我们的软件。另一种情况是,他们已经选择了一个操作系统,
他们会问怎样才能让他们的系统更快地运行我们的应用程序。另外,我们运营了一个主机
托管(hosting)部门,我们希望在为我们的托管客户提供最好性能的前提下尽量降低我们
的硬件成本。
大多互联网应用程序都是按照下面的步骤进行工作的:


接受一个TCP/IP连接请求或者与另外一台计算机建立一个连接;

一旦建立了连接,通过TCP/IP 交换各种各样基于文本的命令;

这些命令会引发不同的行为,如读磁盘(例如浏览网页)、写磁盘(例如将一条接收到的
电子邮件消息进行排队)或者调用外部功能(例如邮件过滤、反向DNS查找等)。
通常,一个网络应用程序涉及的性能问题包括:

用尽可能快的速度完成大量的并发任务;

有效地处理大量的等待(由慢速的TCP/IP连接引起或者等待连接的另一端发送下一个命令);

有效地执行TCP/IP操作。
为了设计最佳性能的网络应用程序,应用程序的软件设计人员必需选择一个充分考虑了上面
的性能准则的软件体系结构。两个最主要的因素是任务体系结构和TCP/IP调用体系结构。
任务体系结构
在任务体系结构领域,有三个主要的技术:

每个任务一个进程(面向进程的)——运行程序的很多复本,每个复本每次只处理一个任务。
有时候,每次创建一个新任务都需要相应地创建一个新的进程(如inetd、Sendmail),
但有些也设计成可以重用进程(如Apache)。这种体系结构在低负载的情况下会产生很好的
性能。在运行中等程度的负载时,如果进程影象比较小(如qmail)、已经实现了针对该应
用程序的效率改进或者该类型的应用程序不会创建太多的同时任务时,其性能也还可以。
如果采用了进程缓冲技术并且同时运行的进程总数不是很多(如中低程度的负载),就可以
采用多CPU来解决问题。这种技术在所有的操作系统上都可以实现,然而,从实现的的角度
讲,UNIX明显地要比Windows更有效。(Windows没有fork()系统调用,很少有Windows应用
程序采用这种技术,因为它在Windows上实在太慢了)。

每个任务一个线程(多线程)——只运行该程序的一个复本,在这个复本中,每个任务都
由一个单独的执行线程来处理。多线程应用程序在低-中负载情况下的性能非常好,在更高
的负载下,其性能就明显下降了(但通常也是可以接受的)。然而,超高负载会将多线程
应用程序带入死亡旋涡(death-spiral)。通常情况下,多线程应用程序最多可以处理500
到1000个并发的任务。这在大多情况下是可以接受的。每个新认为使用一个新的线程,
相对于新进程而言,新线程消耗更少的内存和更少的CPU处理能力。仅仅最流行的UNIX变
种(通常是商用操作系统)才能够在沉重的多线程负载下保持稳定,所以很少有开放源码工
程采用多线程技术。这种技术在多CPU上的性能可能比在单CPU上的性能还要差,因为多处理
器计算机上的信号量(semaphore)锁处理的代价非常高。(多线程软件的例子有Netscape Web server和Apache on Windows)

一个线程处理多个任务(异步处理)——程序的一个复本运行固定数量的线程(通常每种类
型的任务对应一个线程),每个线程通过一种称为异步(非阻塞)TCP/IP的技术处理大量的
同类型任务。因为大多数程序根本不需要处理高负载,并且异步编程非常困难,所以很少有
支持这种技术的程序出现。异步程序可以在多CPU计算机上平滑扩展,因为它们大多采用彼
此独立的长效线程。这种技术很少需要跨CPU的锁,因此每个线程都可以永久且有效地分配
给一个单独的CPU(如DNS BIND守侯进程daemon)

TCP/IP调用体系结构
第二个影响性能的主要因素是TCP/IP调用体系结构。在操作系统级,有多种方法可以实现
相同的网络操作。TCP/IP的速度与优化程序设计之间存在着权衡的问题(更快的技术对编程
人员来说意味着更多的工作)。加之,更快的技术并不是对所有的平台都可用;更高的性能
需求可能会限制平台选择的自由度。

阻塞式TCP/IP调用
阻塞式TCP/IP调用等待所有被请求的操作完成后立即对结果进行操作。在任务数量比较少时,
操作结果会在事件发生时立即予以响应;但在任务数量比较大的情况下,会导致操作系统的
巨量上下文切换系统开销,这时的效率相当低。在低负载的情况下,阻塞(同步)TCP/IP调
用产生很少的等待时间,这对低负载的Web服务器一类的应用来说非常理想(页面响应非常
迅速,条件是负载永远不会太高)。然而,如果使用的是面向进程的体系结构,且每一个新
的网络连接都要建立一个相应的新进程(如inetd)时,阻塞TCP/IP所带来的等待时间上的
性能就会被运行一个新进程所导致的显著的系统调用迅速抵消。

非阻塞TCP/IP调用
非阻塞(异步)TCP/IP调用发起一个操作后就转而进行其它的工作。当操作完成或者发生了
一个事件,这个进程就会获得通知并进行相应的响应。这种两不处理模式需要更多的编程工
作,有时响应新事件会花费少量的时间(增加了等待延时)。在中-高负载的情况下,这种
非阻塞技术会产生更好的执行性能,并且可以避免讨厌的高负荷,但同阻塞TCP/IP调用相比,
等待延时可能稍微长了点儿。
上述的每一个任务处理体系结构都与相应的TCP/IP系统调用模型相匹配。面向进程和多线程
的程序可能会趋向于采用阻塞TCP/IP调用,因为这是编程的最简单方法并且大多数情况下只
需要处理低负载。然而采用异步任务体系结构的应用程序就必需使用非阻塞的TCP/IP操作以
处理多任务了:根本不能选择阻塞TCP/IP。因此,如果您发现一个网络应用程序采用了高度
可扩展的异步任务体系结构,您当然也是从其所采用的最具扩展性的TCP/IP调用体系结构(非阻塞)上获得了不少好处。

真实环境测试
为了评估不同操作系统和网络应用程序的性能,我们进行了三类不同的测试:真实环境、磁
盘I/O和任务体系结构比较。我们检测的操作系统包括Linux (Red Hat 7.0, kernel 2.2.16-22)、
Solaris 2.8 for Intel、FreeBSD 4.2和Windows 2000 Server。这些操作系统都是可获得的
商业版本中最新的,并且没有重新编译(或者说我们将操作系统软件开包安装后就进行了测试)。
我们在相同的4-GB SCSI-3驱动器上(IBM 型号是 DCAS-34330)安装了上述操作系统,并且在相同
的机器(ASUS P3B主板、Intel Pentium III 550-MHz处理器、384-MB SDRAM、Adaptec 2940UW SCSI控制器、
ATI Rage Pro 3D显卡、Intel EtherExpress Pro 10/100 Ethernet网卡)上进行测试。
我们采用测试我们自己开发的MailEngine软件的邮件发送速度来进行真实环境测试。
MailEngine是一个邮件发送服务器,它可以移植到所有要进行测试的平台上(其实还包
括Sparc上的Solaris),它采用的是异步体系结构(带有采用poll()系统调用的非阻
塞TCP/IP)。为了不将电子邮件真的发送到具有200,000个成员的测试列表,我们是在测试
模式下运行的MailEngine。在这种模式下,MailEngine实现发送邮件的所有步骤但是最后
使用RSET命令而不是DATA命令。这样就在真正发送数据之前退出QUIT了SMTP连接,这样就
不会向接收者发送任何电子邮件了。我们的工作量是将一条消息发送到分布在9113个域下
的200,000个电子邮件地址。因为是同一条消息在内存中为所有的接收者进行排队,所以
磁盘I/O并不是影响性能的主要因素。我们缓慢地提高同时连接的数量,以便观察负载的提
升对性能的影响。

图1 操作系统的比较

图1(操作系统的比较)显示了在MailEngine的测试模式中,不同操作系统上一定范围的
同时连接数量下的电子邮件传输速度的测试结果。Linux在速度上占有明显的优势,它比
排在第二位的Solaris快约35%。总体上来说,性能随着连接数量的增加而提高,这张图也
显示速度的增长边际超过1500个连接。FreeBSD在加到多于1500个连接时的性能会有些下降。
在UNIX风格的操作系统上,需要适当的调整内核以便允许在一个进程中使用如此多的连接。
除内核调整外,当负载超过2500个连接时,FreeBSD还会给出资源不足的警告并且停止运行。

文件系统测试
许多网络应用程序同样需要将信息在硬盘上进行排队以便以后处理(如Sendmail的邮件队
列)或处理溢出情况的能力。为了模仿在典型情况文件系统的效率,我们写了一个在单个
目录下创建、写入和读回10,000个文件的C++程序,它每次只操作一个文件。为了全面测试
文件系统对不同种类文件的整体效率,测试文件的大小从4 KB到128 KB递增。

图2 创建、写和读10,000个文件所需要的时间

图2显示了文件系统的测试结果。Linux和Windows的速度基本相同,它们都比另外两个明显
地快得多:是FreeBSD的6倍、Solaris的10倍。每个操作系统所使用的文件系统分别是:
Linux - EXT2, Solaris - UFS, Windows 2000 - NTFS, FreeBSD - UFS。其它的文件系统
无疑将会产生不同的性能结果。如果您的软件应用程序严重依赖于磁盘,我建议您使用Linux
或Windows或者其它运行于FreeBSD或Solaris上的替代文件系统。

应用程序体系结构测试
最后,我们不同的网络应用程序体系结构在每一操作系统上的表现进行了测试。我们写了
一个简单的C++服务程序,它对每一个连接请求都给出一个"450 too busy"的响应消息。
我们程序测试的三个体系结构是:(1)基于进程的体系结构,对每个连接都产生一个新的
进程予以处理;(2)多线程体系结构,为每个进程分配一个线程;(3)异步体系结构,
所有连接都使用非阻塞的TCP/IP予以应答。一个独立的C++程序运行在另外一台计算机
(使用Linux操作系统)上,它试图以最快的速度连接我们的服务器,缓慢增加同时的连接
负载并且计算成功接收的响应信息。对于本文来说,这么多的测试结果图(12个)显然太多了,
因此我们绘制了每个任务体系结构的平均值以便显示大体上的性能差异。

图3 每种网络结构的平均吞吐量

图3显示了每种类型的任务体系结构在所有操作系统平台上的平均性能。虽然不同平台上性
能的差异是非常明显的,但其显著性与体系结构的选择所造成的显著性差异还是相去甚远。
最慢的网络应用体系结构是基于进程的结构,它对连接的处理能力只有异步方法的5%。
在1000个同时连接的情况下,同基于线程的方法相比,异步方法的负载能力要高出35%。
趋势线显示,随着负载的增长,多线程方法与异步方法之间的性能差异将拉大。

为高性能应用进行内核调整
在缺省配置中,我们测试的UNIX风格的操作系统并不支持多线程和异步程序所需要的如此
大量的同时TCP/IP连接。这种限制严重地限制了应用程序的性能,甚至错误地规劝系统管
理员不要采用这些高性能体系结构。值得庆幸的是,这些限制可以容易地通过内核调整来
克服。在UNIX上,每个TCP/IP连接使用一个文件描述符,所以您必需提高操作系统上可以
提供的文件描述符的总量,同时也要提高每个进程允许使用的描述符的最大值。所有的
UNIX风格的操作系统都有一个ulimit的shell命令(sh和bash),这个命令可以让使用它
进行了适当的内核调整之后的shell上运行的命令可以打开更多的文件描述符。我们建议
使用ulimit -n 8192。这里是我们推荐的内核调整过程:
在Linux上: echo 65536 > /proc/sys/fs/file-max改变系统可提供的文件描述符的数量。
在FreeBSD上:将下面这些内容追加到 /etc/sysctl 文件的末尾(或者,您可以使用 sysctl -w 命令来填加这些内容):
kern.maxfiles=65536
kern.maxfilesperproc=32768
在Solaris上:将下面的内容加入 /etc/system 文件后reboot:
set rlim_fd_max=0x8000
set rlim_fd_cur=0x8000

提要
我们的真实环境测试显示,操作系统最好与最差的性能差距达到了75%,Linux拥有比排在
第二位的Solaris还要高出35%的卓越性能。更重要的是,异步应用程序比基于进程的应用
程序平均快12倍,比多线程的应用程序快35%。如果磁盘I/O占用了应用程序运行时间的主要
部分,那么在Linux和Windows 2000上的磁盘I/O任务的处理速度要比Solaris快10倍以上,
比FreeBSD也要快出6倍之多!
如果您正在评估一个网络应用软件,并且它的最终性能对您来说至关重要,那么软件的体系
结构将是一个关键的评估准则(或者说,你应该选择多线程或异步结构)。
 
不错,继续.
至今没有一个用完成端口模式的tcp/IP的VCL控件,包括ics,indy,IPro,dxsock,fastnet等
大家有没有想过主要是什么原因,如果有可能,大家解决其中相关难题,做成一个好用的
vcl控件,岂非一劳永逸?
 
从楼上的文章看,一、太老了点,
二、并没有用什么新技术,他所谓的异步估计就是用select,因为这个可以做
到跨平台。。。,但是这个效率不高,MSDN里有文章说明的...
总结,这篇文章没有太多可以参考的东西...
 
to lynu:
softdog就有一个完成端口的控件,在playicq上
 
关于SOFTDOG那个控件,我发现有点问题:
1、不能预先设置ACTIVE:=TRUE,要先START,因为代码的逻辑处理有问题,START和SetActive中
,如果ACTIVE=TRUE就不做处理;
2、建议把
TOnDataRecive = procedure(IOData: PPerHandleData; var OperPosted: boolean) of object;
改成
TOnDataRecive = procedure(IOData: PPerHandleData; var OperPosted: boolean; RecvLen:dword) of object;
然后在处理接受数据的procedure TIOCPProcThread.Execute中,改成如下(其实就是返回实际接受的数据长度):
ssRecv:
begin
OperPosted := false;
if Assigned(FServerComm.FOnDataRecive) then
FServerComm.FOnDataRecive(HandleData, OperPosted,byteRece);//////********
因为HandleData.wsaBuffer.len始终是BUFFER_SIZE,没有办法得到实际的数据长度。
 
完成端口还是不错的,我的ECHOSERVER,本机开了2100个客户连接,数据流量在
(R:2MBYTE/S:2MBYTE)-(R:6MBYTE/S:6MBYTE)之间变动,效果不错,当然CPU也是
100了,不过我的是P3 700手提,用专门的服务器效果应该好很多。现在在做测试,我照样
能够上网。
 
有学了不少
不过想问个基础的:什么叫阻塞?什么叫异步?
谁能用通俗的话介绍一下?
这个问题太基础了,因为以前没接触过。。。
 
我找的一个东西:
其实,之所以大多数WINDOWS下的INTERNET程序都使用异步模式,这和WINSOCK的历史有关。当WINSOCK被移植到WINDOWS的时候,当时的WINDOWS操作系统还是WINDOWS 3.1,而WINDOWS 3.1是不支持多线程的,不象UNIX下可以使用FORK来运行多进程。在WINDOWS 3.1下,如果使用阻塞模式,在通讯时会锁定用户界面使程序没有响应,为了避免这种情况,WINSOCK就引入异步模式这个新特性。而使用异步模式来编制INTERNET程序也就成了WINDOWS程序员的经典教条。但是,随着新的WINDOWS操作系统的出现,如WINDOWS 95、NT、98、ME、2000等,这些操作系统开始支持多线程。异步模式这个教条仍然深入人心,使很多程序员会下意识的拒绝使用阻塞模式。
 
我已经做好了一个完成端口的VCL,正在做测试,谁能够让我把它传到网上我就谢谢了。
刚才楼上说的不支持Active=true的问题在我这不存在。

评说softdog的控件:
1.使用overlappedplus结构也不是不行,但封装的东西太少,至少我的控件基本上不需要
使用者明白完成端口的机制,如同一般的Socket控件一般用。我使用流来完成。
2.很多完成端口的应用不支持以并发方式向客户发送数据。一般完成端口(包括重叠IO)都
只用一个overlapped,读取数据的时候不能写,写的时候也不能读。softdog的控件能够支
持并发写,无法处理缓存与发送数据之间的数据空间差异。如果需要发送的数据超过2048
字节就无法自动实现。我的控件使用内存流,在发送数据的时候直接通过内存流的memory
做缓冲区。当然,如果数据太大需要使用者事先拆分。我个人认为每个包16K左右比较好。
即使如此,在线程中返回的已写字节数还不一定等于实际包大小,这时需要修改包的大小
并再次投递写IO。softdog的控件显然不支持这种机制。
3.在设计时也能够将Active设为true。我认为这是设计VCL的常识,控件应该能够判断控件
状态处在设计时还是运行时,然后安排控件的操作。
 
还有一个问题不知道各位是否考虑过:如何处理服务器与客户机的同步?是不是需要在
会话层来完成?
 
我是不不建议写成控件,我认为用VC++的办法,直接用类,从TObject下继承
用内存池、或者对象池来进行内存分配和使用,减少系统消耗...
 
使用对象真的能增加服务器的系统开销?这个问题我们可以重新开个贴来研究。在这里我
保留不同意见。我不认为用对象操作会比API调用慢很多。可能会有一点点影响,估计时间
效率上不会超过1%,空间效率上不会超过5%。难道你不希望你的代码下次读的时候轻松一
些吗?
 
TO BARTON:
你可以放到WWW.PLAYICQ.COM
 
我看了你的一些代码,你每多一个连接,你就建一个对象,离开的时候释放,
如果对方攻击你,加入连接但是不发数据,你怎么办?反复的创建和释放,
容易有内存脆片你怎么解决?
 
我也正在写准备想写这方面的东东,但对于它里面的东东不够深入,写到一半写不下去。。
这段也没有多少时间。。。关注此贴。。。

这是我写的。。。
http://www.playicq.com/datanew/demo_iocp2.rar
 
错了,发错了,请下载这个。。。
http://www.playicq.com/datanew/demo_iocp2.rar
:(
 
线程阻塞来说对于Win和Linux平台也说都是可用的,但IO重叠只是在Win平台适用,所以
Borland的产品最好是重写的次数越少越好,因为她还有兼Kylix发展,所以我看它写成
阻塞+多线程的方式,这个是一个很大的理由。。
我看了D7的源代码,它是准备再用Indy重写TSocketConnection,即MIDAS(Socket)方式,
对于MIDAS,Borland已经有了一个比较好通讯方式,所以我觉得她应该是不会放弃这个
东东,而且多线程+阻塞在Linux下平台下运行,比之WIN平台的效率应该是会好的多。。。
这就。。。呵呵,难保以后的MIDAS中间层不会慢慢地向Linux平台发展了。

多线程+阻塞操作我也觉得比较好控制,因为一个线程就是一个会话,如果是非阻塞的情况,
还要多了一个需要去维护的东西,就是状态了。有时这个状态很难搞,所以,我一明白多
线程+阻塞的道理后,就比较少去深入接触非阻塞的情况。
 
顶部