c10k问题中文版

翻译前言:
本来翻译学术论文应该本着严谨、准确的态度,否则还不如不去翻译。
可是翻译这篇文章挺不容易。以我的时间和精力目前只能翻译到这种程度。以后可能会随原文进行同步更新。一些地方还需要进行推敲。
译文部分参考了网友 f_acme 文章。本来想把文章的地址一并贴在这里,今天发现,这篇文章已经被删去。 在此感谢f_acme,是他使我有了翻译这篇文章的冲动。
翻译日志:
更新:2008年6月23日
 
 
f_acme
 
如今的 web 服务器需要同时处理一万个以上的客户端了,难道不是吗?毕竟如今的网络上访问量已经非常巨大了。
现在的计算机非常强大,你只需要花大概 $1200 就可以买一个 1000MHz 的处理器, 2G 的内存, 1000Mbit/sec 的网卡的机器。让我们来看看 --20000 个客户,每个为 50KHz 100Kbyes 50Kbit/sec ,那么没有什么比为这两万个客户端的每个每秒从硬盘读取 4 千字节然后发送到网络上 去更消耗资源的了。可以看出硬件不再是瓶颈了。 (That works out to $0.08 per client, by the way. Those $100/client licensing fees some operating systems charge are starting to look a little heavy!)
1999 年最繁忙的 ftp 站点, cdrom.com ,尽管有上 G 比特的网络带宽,却也只能同时处理 10000 客户端。在 2001 年,同样的速度可以被 几个 ISP服务商 所提供,预期该趋势会因为大量的商业用户而变得越来越普遍。
目前的瘦客户端模型也开始再次变得流行起来了 ――这一次是 服务器运行在 Internet 上,为数千个客户端服务。
基于以上一些考虑,这里就配置操作系统或者编写支持数千个网络客户端的代码问题提出一些 需要加以 注意的地方,讨论的范围是我比较感兴趣的类 Unix 操作系统,当然我也喜欢 Windows
内容
·                     The C10K problem
·                     相关 站点
·                     须首先阅读书籍
·                     I/O 框架
·                     I/O 策略
1.                用一个线程来服务多个客户 , 使用非阻塞 I/O nonblocking I/O )和水平触发 [ 译注:也有文档译成条件触发 ] 的就绪通知( level-triggered readiness notification
§                传统的 select()
§                传统的 poll()
§                /dev/poll (Solaris 2.7+)
§                kqueue (FreeBSD, NetBSD)
2.                用一个线程来服务多个客户 , 使用非阻塞的 I/O 和就绪变化通知( readiness change notification
§                epoll (Linux 2.6+)
§                Polyakov's kevent (Linux 2.6+)
§                Drepper's New Network Interface (proposal for Linux 2.6+)
§                实时信号 (Linux 2.4+)
§                每个 fd 一个信号
§                kqueue (FreeBSD, NetBSD)
3.                一个客户一个线程模式 , 使用异步 I/O 和完成通知( completion notification
4.                一个客户一个服务器线程模式
§                LinuxThreads (Linux 2.0+)
§                NGPT (Linux 2.4+)
§                NPTL (Linux 2.6, Red Hat 9)
§                FreeBSD 对线程的支持
§                NetBSD 对线程的支持
§                Solaris 对线程的支持
§                Java JDK 1.3 和早期的对线程的支持
§                Note: 1:1 线程模型 vs. M:N 线程模型
5.                把服务器代码编译进内核
·                     重要注释
·                     打开文件句柄数目上的限制
·                     线程上的限制
·                     Java 问题 [ 更新到 27 May 2001]
·                     Other tips
o                  零拷贝
o                  调用 sendfile() 来实现网络上的零拷贝技术
o                  使用 writev (or TCP_CORK) 避免小的帧
o                  受益于使用非 Posix 线程的程序
o                  缓存你的数据有时会使性能提升
·                     其它限制
·                     核心问题
·                     测试服务器性能
·                     例子
值得关注的基于 select() 服务器
值得关注的基于 /dev/poll 的服务器
值得关注的基于 kqueue() 的服务器
值得关注的基于实时信号的服务器
值得关注的基于线程的服务器
值得关注的内核中的服务器
·                     其它值得关注的链接
相关站点
2003 10 月, Felix von Leitner 整理了一个很好的 网站 和一个 presentation [ 译注:指 Felix von Leitner 所写的关于 scalable network programming 的主题 ] ,该网站介绍了网络 ( 程序 ) 的可伸缩性,完成了以不同网络系统调用和不同的操作系统为基准的性能比较。其中一项就是 2.6 版本的 Linux 内核击败了 2.4 的内核,当然还有许多的测试图表可以给 OS 的开发者在平时提供点参考。 (See also the Slashdot comments; it'll be interesting to see whether anyone does followup benchmarks improving on Felix's results.)
 
需首先阅读的书籍
如果你还没有读过 W.Richard Stevens 先生的 Unix网络编程 :第一卷 的话,推荐阅读,该书描述了许多关于编写高性能的服务器的 I/O 策略和各自的一些缺陷,甚至还讲述 "thundering herd" 问题,同时你也可以阅读 Jeff Darcy写的关于高性能服务器设计 的一些 观点
( 另外有一本书会非常有用,特别是对那些使用而不是开发 web 服务器程序的人。这本书是由 Cal Henderson 所著的《 Building Scalable Web Sites .)
I/O 框架
以下所列的为几个包装好的库,它们抽象出了一些下面所表达的技术,并且可以使你的代码与具体操作系统隔离,从而具有更好的移植性。
·                     ACE , 一个重量级的 C++ I/O 框架,用面向对象实现了一些 I/O 策略和其它有用的东西,特别的, 它的 Reactor 框架 是用 OO 方式处理非阻塞 I/O ,而 Proactor 框架 是用 OO 方式处理异步 I/O 的。
·                     ASIO 一个 C++ I/O 框架,正在成为 Boost 库的一部分。它像是 ACE 过渡到 STL 时代。(译注: ACE 内部有自己的容器实现,它和 C++ 标准库中的容器是不兼容的。)
·                     libevent Niels Provos C 语言编写的一个轻量级的 I/O 框架。它支持 kqueue select ,并且很快就可以支持 poll epoll( 翻译此文时已经支持 ) 。我想它应该是只采用了水平触发机制,该机制功过参半。 Niels 给出了 一张图 来说明时间和连接数目在处理一个事件上的功能,从图上可以看出 kqueue sys_epoll 明显胜出。
·                     我本人也尝试过写一个轻量级的框架 ( 很可惜没有维持至今 ):
o                  Poller 是一个轻量级的 C++ I/O 框架,它使用任何一种准备就绪 API(poll, select, /dev/poll, kqueue, sigio) 实现水平触发准备就绪 API 。以其它 不同的 API为基准 Poller 的性能 好得多。该链接文档的下面一部分说明了如何使用这些准备就绪 API
o                  rn 是一个轻量级的 C I/O 框架,也是我继 Poller 后的第二个框架。该框架可以很容易的被用 于商业应用中,也容易的适用于非 C++ 应用中。它如今已经在几个商业产品中使用。
·                     2000 4 月, Matt Welsh 就构建服务器如何平衡工作线程和事件驱动技术的使用方面写了 一篇 论文 ,在该论文中描述了他自己的 Sandstorm I/O 框架。
·                     Cory Nelson's Scale! library - 一个 Windows 下的异步套接字,文件和管道的 I/O 库。
I/O 策略
网络软件设计者往往有很多种选择,以下列出一些:
·                     是否处理多个 I/O ?如何处理在单一线程中的多个 I/O 调用?
o                  不处理,从头到尾使用阻塞和同步 I/O 调用,可以使用多线程或多进程来达到并发效果。
o                  使用非阻塞调用(如在一个设置 O_NONBLOCK 选项的 socket 上使用 write )读取 I/O ,当 I/O 成时发出通知(如 poll /dev/poll )从而开始下一个 I/O 。这种主要使用在网络 I/O 上,而不是磁盘的 I/O 上。
o                  使用异步调用(如 aio_write() )读取 I/O ,当 I/O 完成时会发出通知(如信号或者完成端口),可以同时使用在网络 I/O 和磁盘 I/O 上。
·                     如何控制对每个客户的服务 ?
  •  
    •  
      • 对每个客户使用一个进程(经典的 Unix 方法,自从 1980 年一直使用)
      • 一个系统级的线程处理多个客户,每个客户是如下一种:
一种用户级的线程 (e.g. GNU state threads, classic Java with green threads)
一个状态机 (a bit esoteric, but popular in some circles; my favorite)
a continuation (a bit esoteric, but popular in some circles)
  •  
    •  
      • 一个系统级的线程对应一个来自客户端的连接 (e.g. classic Java with native threads)
      • 一个系统级的线程对应每一个活动的客户端连接 (e.g. Tomcat with apache front end; NT 完成端口 ; 线程池 )
·                     是否使用标准的操作系统服务,还是把一些代码放入内核中(如自定义驱动,内核模块, VxD )。
下面的五种组合应该是最常用的了。
1.      一个线程服务多个客户端,使用非阻塞 I/O 水平触发 的就绪通知
2.      一个线程服务多个客户端,使用非阻塞 I/O 和就绪改变 时通知
3.      一个服务线程服务多个客户端,使用异步 I/O
4.      一个服务线程服务一个客户端,使用阻塞 I/O
5.      把服务器代码编译进内核
1. 一个线程服务多个客户端,使用非阻塞 I/O 和水平触发的就绪通知
... 把所有的套接字设置为工作在非阻塞模式下,然后使用 select() poll() 来告知哪个句柄已有数据在等待处理。此模型是最传统的,在此模型下,由内核告知你某个文件描述符是否准备好,是否已经完 成你的任务自从上次内核告知已准备好以来( 水平触发 这个名字来源计算机硬件设计,与其相对的是 边缘触发 Jonathon Lemon 在它的 BSDCON 2000 paper on kqueue() 论文 中介绍了这两个术语)。
注意:牢记内核的就绪通知仅仅只是个提示,当你试图从一个文件描述符读取数据时,该文件描述符可能并没有准备好。这就是为什么需要在使用就绪通知的时候使用非阻塞模型的原因。
一个重要的瓶颈是 read() sendfile() 从磁盘块读取时,如果该页当前并不在内存中。设置磁盘文件描述符为非阻塞没有任何影响。同样的问题也发生在内存映射磁盘文件中。首先一个服务 需要磁盘 I/O 时,进程块和所有的客户端都必须等待,因此最初的非线程的性能就被消耗了。
这也是异步 I/O 的目的,当然仅限于没有 AIO 的系统。处理磁盘 I/O 的工作线程或工作进程也可能遭遇此 瓶颈。一条途径就是使用内存映射文件,如果 mincore() 指明 I/O 必需的话,那么要求一个工作线 程来完成此 I/O ,然后继续处理网络事件。 Jef Poskanzer 提到 Pai Druschel Zwaenepoel Flash web服务器 使用了这个方法,并且他们就此在 Usenix'99 上做了一个演讲,看上去就好像 FreeBSD Solaris 中提供了 mincore()一样,但是它并不是 Single Unix Specification 的一部分,在 Linux 2.3.51 的内核中提供了该方法,感谢 Chuck Lever
2003.11 freebsd-hackers list中, Vivek Pei 上报了一个不错的成果,他们利用系统剖析工具剖析它们的 Flash Web 服务器,然后再攻击其瓶颈。其中找到的一个瓶颈就是 mincore (想来 mincore 已经不再是一个好办法了。),另外一个就是 sendfile 在磁盘块访问时的阻塞。通过引进修改过的 sendfile() ,当需要读取的页还没有在内存中时则返回类似 EWOULDBLOCK 的值,从而提高了性能。
The end result of their optimizations is a SpecWeb99 score of about 800 on a 1GHZ/1GB FreeBSD box, which is better than anything on file at spec.org.
在非阻塞套接字的集合中,关于单一线程是如何告知哪个套接字是 I/O 准备就绪的,以下列出了几 种方法 :
·                     传统的 select()
·                     遗憾的是, select() 受限于 FD_SETSIZE 个句柄。该限制被编译进了标准库和用户程序(有些 版本的 C library 允许你在用户程序编译时放宽该限制)。
See Poller_select (cc , h ) for an example of how to use select() interchangeably with other readiness notification schemes.
·                     传统的 poll()
·                     虽然 poll() 能够处理的文件描述符个数没有硬编码限制,但是当有数千个时速度就会变得很慢,因为 大多数的文件描述符在某个时间是空闲的,彻底扫描数千个描述符是需要花费一定时间的。
有些操作系统(如 Solaris 8 )通过使用了 poll hinting 技术改进了 poll() ,该技术由 Niels Provos 1999年实现并利用基准测试程序 测试过。
See Poller_poll (cc , h , benchmarks ) for an example of how to use poll() interchangeably with other readiness notification schemes.
·                     /dev/poll
·                     这是在 Solaris 中被推荐的代替 poll 的方法。
/dev/poll 的背后思想就是利用 poll() 在大部分的调用时使用相同的参数。使用 /dev/poll ,首先打开 /dev/poll 得到文件描述符,然后把你关心的文件描述符写入到 /dev/poll 的描述符, 然后你就可以从 /dev/poll 的描述符中读取到已就绪的文件描述符。
/dev/poll Solaris 7(see patchid 106541 ) 中就已经存在,不过在 Solaris 8 中才公开现身。 750个客户端的情况下 this has 10% of the overhead of poll()
关于 /dev/poll Linux 上有多种不同的尝试实现,但是没有一种可以和 epoll 相比,不推荐在 Linux 上使用 /dev/poll
See Poller_devpoll (cc , h benchmarks ) for an example of how to use /dev/poll interchangeably with many other readiness notification schemes. (Caution - the example is for Linux /dev/poll, might not work right on Solaris.)
·                     kqueue()
·                     这是在 FreeBSD 系统上推荐使用的代替 poll 的方法 (and, soon, NetBSD).
kqueue() 即可以水平触发,也可以边缘触发,具体请看下面 .
2. 一个线程服务多个客户端,使用非阻塞 I/O 和就绪改变时通知
Readiness change notification (或边缘触发就绪通知)( goldou :就绪变化通知)的意思就是当你提供给内核一个文件描述符,一段时间后,如果该文件描述符从没有就绪 到已经准备就绪 ,那么内核就会发出通知,告知该文件描述符已经就绪,并且不会再对该描述符发出类似的就绪通知直到你在描述符上进行一些操作使得该描述符不再就绪(如直到在 send recv 或者 accept 等调用上遇到 EWOULDBLOCK 错误,或者发送 / 接收了少于需要的字节数)。
当使用 Readiness change notification 时,必须准备好处理乱真事件,因为最常见的实现是只要接收到任何数据包都发出就绪信号,而不管文件描述符是否准备就绪。
这是 水平触发 的就绪通知的相对应的机制。这就要求编程中的错误检测要非常严格,否则一旦你错过了一个事件,整个连接就永远卡死了。然而,我发现边缘触发的通知机制可以使编写带 OpenSSL 非阻塞客户端更简单,可以试下。
[Banga, Mogul, Drusha '99] 详细描述了这种模型 .
有几种 APIs 可以使得应用程序获得 文件描述符已就绪 的通知 :
·                     kqueue() 这是在 FreeBSD 系统上推荐使用边缘触发的方法 (and, soon, NetBSD).
FreeBSD 4.3 及以后版本, NetBSD 2002.10 都支持 kqueue()/kevent() 支持边沿触发和水平触发(请查看 Jonathan Lemon 的网页和他的 BSDCon 2000 关于 kqueue 的论文)。
就像 /dev/poll 一样,你分配一个监听对象,不过不是打开文件 /dev/poll ,而是调用 kqueue () 来获得。需要改变你所监听的事件或者获得当前事件的列表,可以在 kqueue() 返回的描述符上 调用 kevent() 来达到目的。它不仅可以监听套接字,还可以监听普通的文件的就绪,信号和 I/O 成的事件也可以 .
Note: 2000.10 FreeBSD 的线程库和 kqueue() 并不能一起工作得很好,当 kqueue() 阻塞时, 那么整个进程都将会阻塞,而不仅仅是调用 kqueue() 的线程。
See Poller_kqueue (cc , h , benchmarks ) for an example of how to use kqueue() interchangeably with many other readiness notification schemes.
使用 kqueue() 的例程和库 :
o                  PyKQueue -- 一个 Python kqueue() .
o                  Ronald F.Guilmette echo的服务器例程 ; 另外可以查看他在 2000.9.28 freebsd 上发表的帖子。
·                     epoll
·                     这是 Linux 2.6 的内核中推荐使用的边沿触发 poll.
2001.7.11 Davide Libenzi 提议了一个实时信号的可选方法,他称之为 /dev/epoll 该方法类似与实时信号就绪通知机制,但是结合了其它更多的事件,从而在大多数的事件获取上拥有更高的效率。
epoll在将它的接口从一个 /dev下的指定文件改变为系统调用 sys_epoll后就合并到 2.5版本的 Linux内核开发树中,另外也提供了一个为 2.4老版本的内核可以使用 epoll的补丁。
unifying epoll, aio, 2002 年万圣节前夕的 Linux内核邮件列表就 统一 epoll aio和其它的 event sources 展开了很久的争论, 未来可能会发生这种情况(指统一),但 Davide 首先还是在集中精力使 epoll 更稳定。
·                     Polyakov's kevent (Linux 2.6+) 的最后新闻: 2006.2.9 2006.7.9 Evgeniy Polyakov 发表了融合 epoll aio 的补丁,他的目标是支持网络 AIO. See:
o                  the LWN article about kevent
o                  his July announcement
o                  his kevent page
o                  his naio page
o                  some recent discussion
·                     Drepper 的最新网络接口 (proposal for Linux 2.6+)
·                     2006 OLS 上, Ulrich Drepper 提议了一种最新的高速异步网络 API. See:
o                  他的论文 , "The Need for Asynchronous, Zero-Copy Network I/O "
o                  他的幻灯片
o                  LWN article from July 22
·                     实时信号( Realtime Signals
·                     Linux2.4 内核中推荐使用的边沿触发 poll.
2.4 linux 内核可以通过实时信号来分派套接字事件 , 示例如下 :
/* Mask off SIGIO and the signal you want to use. */
 sigemptyset(&sigset);
sigaddset(&sigset, signum);
 sigaddset(&sigset, SIGIO);
sigprocmask(SIG_BLOCK, &m_sigset, NULL);
/* For each file descriptor, invoke F_SETOWN, F_SETSIG, and set O_ASYNC. */
fcntl(fd, F_SETOWN, (int) getpid());
fcntl(fd, F_SETSIG, signum);
flags = fcntl(fd, F_GETFL);
flags |= O_NONBLOCK|O_ASYNC;
fcntl(fd, F_SETFL, flags); 
当正常的 I/O 函数如 read() write() 完成时,发送信号。要使用该段的话,在外层循环中编写 一个普通的 poll() ,在循环里面,当 poll() 处理完所有的描述符后,进入 sigwaitinfo() 循环。 如果 sigwaitinfo() sigtimedwait() 返回了实时信号,那么 siginfo.si_fd siginfo_si_band 给出的信息和调用 poll() pollfd.fd pollfd.revents 的几乎一样。如果你处 理该 I/O ,那么就继续调用 sigwaitinfo()
如果 sigwaitinfo() 返回了传统的 SIGIO ,那么信号队列溢出了,你必须通过临时 改变信号处理 程序为 SIG_DFL来刷新信号队列 ,然后返回到外层的 poll() 循环。
See Poller_sigio (cc , h ) for an example of how to use rtsignals interchangeably with many other readiness notification schemes.
See Zach Brown's phhttpd 示例代码来如何直接使用这些特点 . (Or don't; phhttpd is a bit hard to figure out...)
[Provos, Lever, and Tweedie 2000 ] 描述了最新的 phhttp 的基准测试,使用了不同的 sigtimewait() sigtimedwait4() ,这些调用可以使你只用一次调用便获得多个信号。 有趣的是, sigtimedwait4() 的主要好处是它允许应用程序测量系统负载 (so it could behave appropriately ) poll() 也提供了同样的系统负载 测量)。
·                     Signal-per-fd
·                     Signal-per-fd 是由 Chandra Mosberger 提出的对实时信号的一种改进,它可以减少甚至削除实 时信号的溢出通过 oalescing redundant events 。然而是它的性能并没有 epoll . 论文 (www.hpl.hp.com/techreports/2000/HPL-2000-174.html ) 比较了它和 select() /dev/poll 的性能 .
Vitaly Luban 2001.5.18公布了一个实现 Signal-per-fd的补丁 ; 授权见 www.luban.org/GPL/gpl.html . ( 2001.9 ,在很重的负载情况下仍然存在稳定性问题,利用 dkftpbench 测试在 4500 个用户时将引发问题 .
See Poller_sigfd (cc , h ) for an example of how to use signal-per-fd interchangeably with many other readiness notification schemes.
3. 一个服务线程服务多个客户端,使用异步 I/O
该方法目前还没有在 Unix 上普遍的使用,可能因为很少的操作系统支持异步 I/O ,或者因为它你需要重新修改应用程序 (rethinking your applications) 在标准 Unix 下,异步 I/O 是由 "aio_"接口 提供的,它把一个信号和值与每一个 I/O 操作关联起来。信号和其值的队列被有效地分配到用户的 进程上。异步 I/O POSIX 1003.1b 实时标准的扩展,也属于 Single Unix Specification,version 2.
AIO 使用的是边缘触发的完成时通知,例如,当一个操作完成时信号就被加入队列(也可以使用 水平触发的完成时通知,通过调用 aio_suspend() 即可, 不过我想很少人会这么做) .
glibc 2.1 和后续版本提供了一个普通的实现,仅仅是为了兼容标准,而不是为了获得性能上的提高。
Ben LaHaise 编写的 Linux AIO 实现合并到了 2.5.32 的内核中,它并没有采用内核线程,而是使 用了一个高效的 underlying api ,但是目前它还不支持套接字( 2.4 内核也有了 AIO 的补丁,不过 2.5/2.6 的实现有一定程序上的不同)。更多信息如下 :
·                     The page "Kernel Asynchronous I/O (AIO) Support for Linux " 设法关于 2.6 内核中异步 I/O 的实现的所有的信息集中在一起。 ( 发表于 16 Sept 2003)
·                     Round 3: aio vs /dev/epoll by Benjamin C.R. LaHaise (presented at 2002 OLS)
·                     Asynchronous I/O Suport in Linux 2.5 , by Bhattacharya, Pratt, Pulaverty, and Morgan, IBM; presented at OLS '2003
·                     Design Notes on Asynchronous I/O (aio) for Linux by Suparna Bhattacharya -- compares Ben's AIO with SGI's KAIO and a few other AIO projects
·                     Linux AIO home page - Ben's preliminary patches, mailing list, etc.
·                     linux-aio mailing list archives
·                     libaio-oracle - library implementing standard Posix AIO on top of libaio. First mentioned by Joel Becker on 18 Apr 2003 .
Suparma 建议先看看 AIO API .
RedHat AS Suse SLES 都在 2.4 的内核中提供了高性能的实现,与 2.6 的内核实现相似,但并不完全一样。
2006.2 ,在网络 AIO 有了一个新的尝试,具体请看 Evgeniy Polyakov 的基于 kevent AIO.
1999 SGI Linux实现了一个高速的 AIO ,在到 1.1版本时,据说可以很好的工作于磁盘 I/O和网络套接字,且使用了内核线程。目前该实现依然对那些不能等待 Ben AIO套接字支持的人来说是 很有用的。
O'Reilly "POSIX.4: Programming for the Real World" 一书对 aio 做了很好的介绍 .
这里 有一个指南介绍了早期的非标准的 aio 实现,可以看看,但是请记住你得把 "aioread" 转换为 "aio_read"
注意 AIO 并没有提供无阻塞的为磁盘 I/O 打开文件的方法,如果你在意因打开磁盘文件而引起 sleep 的话, Linus建议 你在另外一个线程中调用 open() 而不是把希望寄托在对 aio_open() 系统调用上。
Windows 下,异步 I/O 与术语 " 重叠 I/O" "IOCP"(I/O Completion Port,I/O 完成端口 ) 有一定联系。 Microsoft IOCP 结合了 先前的如异步 I/O( aio_write) 的技术,把事件完成的通知进行排队 ( 就像使用了 aio_sigevent 字段的 aio_write), 并且它 为了保持单一 IOCP 线程的数量从而阻止了一部分请求。( Microsoft's IOCP combines techniques from the prior art like asynchronous I/O (like aio_write) and queued completion notification (like when using the aio_sigevent field with aio_write) with a new idea of holding back some requests to try to keep the number of running threads associated with a single IOCP constant.
更多信息请看 Mark russinovich sysinternals.com 上的文章 深入解析 I/O 完成端口( Inside I/O Completion Ports ), Jeffrey Richter 的书 "Programming Server-Side Applications for Microsoft Windows 2000" (Amazon , MSPress ), U.S. patent #06223207 , or MSDN .
4. 一个服务线程服务一个客户端,使用阻塞 I/O
... read() write() 阻塞 . 这样不好的地方在于需要为每个客户端使用一个完整的栈,从而比较浪费内存。 许多操作系统仍在处理数百个线程时存在一定的问题。如果每个线程使用 2MB 的栈,那么当你在 32 位的机器上运行 512 2^30 / 2^21=512 )个线程时,你就会用光所有的 1GB 的用户可访问虚拟内存( Linux 也是一样运行在 x86 上的)。 你可以减小每个线程所拥有的栈内存大小,但是由于大部分线程库在一旦线程创建后就不能增大线程栈大小,所以这样做 就意味着你必须使你的程序最小程度地使用内存。当然你也可以把你的程序运行在 64 位的处理器上去。
Linux FreeBSD Solaris 系统的线程库一直在更新, 64 位的处理器也已经开始在大部分的用户中所使用。 也许在不远的将来,这些喜欢使用一个线程来服务一个客户端的人也有能力服务于 10000 个客户了。 但是在目前,如果你想支持更多的客户,你最好还是使用其它的方法。
For an unabashedly pro-thread viewpoint, see Why Events Are A Bad Idea (for High-concurrency Servers) by von Behren, Condit, and Brewer, UCB, presented at HotOS IX. Anyone from the anti-thread camp care to point out a paper that rebuts this one? :-)
LinuxThreads
LinuxTheads 是标准 Linux 线程库的命名。 它从 glibc2.0 开始已经集成在 glibc 库中,并且高度兼容 Posix 标准,不过在性能和信号的支持度上稍逊一筹。
NGPT: Next Generation Posix Threads for Linux 下一代 Linux Posix 线程
NGPT 是一个由 IBM 发起的项目,其目的是提供更好的 Posix 兼容的 Linux 线程支持。 现在已到 2.2 稳定版,并且运行良好 ... 但是 NGPT team 公布 他们正在把 NGPT 的代码基改为 support-only 模式,因为他们觉得这才是支持社区长久运行的最好的方式。 NGPT 小组将继续改进 Linux 的线程支持,但主要关注 NPTL 方面。 (Kudos to the NGPT team for their good work and the graceful way they conceded to NPTL.)
NPTL: Native Posix Thread Library for Linux(Linux 本地 Posix 线程库 )
NPTL 是由 Ulrich Drepper ( glibc 的主要维护人员 ) Ingo Molnar 发起的项目,目的是提供世界级的 Posix Linux 线程支持。
2003.10.5 NPTL 作为一个 add-on 目录(就像 linuxthreads 一样)被合并到 glibc cvs 树中,所以很有可能随 glibc 的下一次 release 一起发布。
Red Hat 9 是最早的包含 NPTL 的发行版本(对一些用户来说有点不太方便,但是必须有人来打破这沉默 [break the ice]...)
有用的 NPTL 链接 :
·                     NPTL讨论的邮件列表
·                     NPTL源码
·                     NPTL的最初发表
·                     最初的描述 NPTL目标的白皮书
·                     修改的 NPTL的最后设计的白皮书
·                     Ingo Molnar 最初的基准测试表明可以处理 10^6 个线程
·                     Ulrich的基准测试 比较了 LinuxThreads NPTL IBM NGPT 的各自性能,结果看来 NPTL NGPT 快的多。
这是我尝试写的描述 NPTL 历史的文章 ( 也可以参考 Jerry Cooperstein的文章 ):
2002.3 NGPT小组的 Bill Abt glibc的维护者 Ulrich Drepper 和其它人召开了个会议 来探讨 LinuxThreads 的发展,会议的一个 idea 就是要改进 mutex 的性能。 Rusty Russell 等人 随后实现了 fast userspace mutexes (futexes) , (如今已在 NGPT NPTL 中应用了)。 与会的大部分人都认为 NGPT 应该合并到 glibc 中。
然而 Ulrich Drepper 并不怎么喜欢 NGPT ,他认为他可以做得更好。 ( 对那些曾经想提供补丁给 glibc 的人来说,这应该不会令他们感到惊讶 :-) 于是在接下来的几个月里, Ulrich Drepper, Ingo Molnar 和其它人致力于 glibc 和内核的改变,然后就弄出了 Native Posix Threads Library (NPTL). NPTL 使用了 NGPT 设计的所有内核改进( kernel enhancement ),并且采用了几个最新的改进。 Ingo Molnar描述了 一下的几个内核改进:
NPTL 使用了三个由 NGPT 引入的内核特征 : getpid() 返回 PID CLONE_THREAD futexes; NPTL 还使用了 ( 并依赖 ) 也是该项目的一部分的一个更为 wider 的内核特征集。
一些由 NGPT 引入内核的 items 也被修改,清除和扩展,例如线程组的处理 (CLONE_THREAD). [the CLONE_THREAD changes which impacted NGPT's compatibility got synced with the NGPT folks, to make sure NGPT does not break in any unacceptable way.]
这些为 NPTL 开发的并且后来在 NPTL 中使用的内核特征都描述在设计白皮书中, http://people.redhat.com/drepper/nptl-design.pdf ...
A short list: TLS support, various clone extensions (CLONE_SETTLS, CLONE_SETTID, CLONE_CLEARTID), POSIX thread-signal handling, sys_exit() extension (release TID futex upon VM-release), the sys_exit_group() system-call, sys_execve() enhancements and support for detached threads.
There was also work put into extending the PID space - eg. procfs crashed due to 64K PID assumptions, max_pid, and pid allocation scalability work. Plus a number of performance-only improvements were done as well.
In essence the new features are a no-compromises approach to 1:1 threading - the kernel now helps in everything where it can improve threading, and we precisely do the minimally necessary set of context switches and kernel calls for every basic threading primitive.
NGPT NPTL 的一个最大的不同就是 NPTL 1:1 的线程模型,而 NGPT M:N 的编程模型 ( 具体请看下面 ). 尽管这样, Ulrich的最初的基准测试 还是表明 NPTL NGPT 快很多。 (NGPT 小组期待查看 Ulrich 的测试程序来核实他的结果 .)
FreeBSD 线程支持
FreeBSD 支持 LinuxThreads 和用户空间的线程库。同样, M:N 的模型实现 KSE FreeBSD 5.0 中引入。 具体请查看 www.unobvious.com/bsd/freebsd-threads.html .
2003.3.25, Jeff Roberson 发表于 freebsd-arch :
... 感谢 Julian, David Xu, Mini, Dan Eischen, 和其它的每一位参加了 KSE libpthread 开发的成员所提供的基础, Mini 和我已经开发出了一个 1 1 模型的线程实现,它可以和 KSE 并行工作而不会带来任何影响。 It actually helps bring M:N threading closer by testing out shared bits. ...
And 2006.7, Robert Watson提议 1:1的线程模型应该为 FreeBSD 7.x的默认实现 :
我知道曾经讨论过这个问题,但是我认为随着 7.x 的向前推进,这个问题应该重新考虑。 在很多普通的应用程序和特定的基准测试中, libthr 明显的比 libpthread 在性能上要好得多。 libthr 是在我们大量的平台上实现的,而 libpthread 却只有在几个平台上。 最主要的是因为我们使得 Mysql 和其它的大量线程的使用者转换到 "libthr" which is suggestive, also! ... 所以 strawman 提议 : libthr 成为 7.x 上的默认线程库。
NetBSD 线程支持
根据 Noriyuki Soda 的描述 :
内核支持 M:N 线程库是基于调度程序激活模型,合并于 2003.1.18 当时的 NetBSD 版本中。
详情请看 Nathan J. Williams, Wasabi Systems, Inc. 2002 年的 FREENIX 上的演示 An Implementation of Scheduler Activations on the NetBSD Operating System
Solaris 线程支持
Solaris 的线程支持还在进一步提高 evolving... Solaris 2 Solaris 8 ,默认的线程库使用的都是 M:N 模型 , 但是 Solaris 9 却默认使用了 1:1 线程模型 . 查看 Sun多线程编程指南 Sun的关于 Java Solaris线程的 note .
Java JDK 1.3.x 及更早的线程支持
大家都知道, Java 一直到 JDK1.3.x 都没有支持任何处理网络连接的方法,除了一个线程服务一个客户端的模型之外。 Volanomark 是一个不错的微型测试程序,可以用来测量在 某个时候不同数目的网络连接时每秒钟的信息吞吐量。在 2003.5, JDK 1.3 的实现实际上可以同时处理 10000 个连接,但是性能却严重下降了。 Table 4 可以看出 JVMs 可以处理 10000 个连接,但是随着连接数目的增长性能也逐步下降。
Note: 1:1 threading vs. M:N threading
在实现线程库的时候有一个选择就是你可以把所有的线程支持都放到内核中(也就是所谓的 1 1 的模型),也可以 把一些线程移到用户空间上去(也就是所谓的 M N 模型)。从某个角度来说 , M:N 被认为拥有更好的性能,但是由于很难被正确的编写, 所以大部分人都远离了该方法。
·                     为什么 Ingo Molnar相对于 M N更喜欢 1 1
·                     Sun改为 1 1的模型
·                     NGPT Linux 下的 M N 线程库 .
·                     Although Ulrich Drepper计划在新的 glibc线程库中使用 M N的模型 , 但是还是 选用了 1 1的模型 .
·                     MacOSX 也将使用 1 1的线程 .
·                     FreeBSD NetBSD 仍然将使用 M N 线程, FreeBSD 7.0 也倾向于使用 1 1 的线程(见上面描述),可能 M N 线程的拥护者最后证明它是错误的。
5. 把服务代码编译进内核
Novell Microsoft 都宣称已经在不同时期完成了该工作,至少 NFS 的实现完成了该工作。 khttpd Linux 下为静态 web 页面完成了该工作, Ingo Molnar 完成了 "TUX" (Threaded linUX webserver) ,这是一个 Linux 下的快速的可扩展的内核空间的 HTTP 服务器。 Ingo 2000.9.1宣布 alpha 版本的 TUX 可以在 ftp://ftp.redhat.com/pub/redhat/tux 下载 , 并且介绍了如何加入其邮件列表来获取更多信息。
Linux 内核的邮件列表上讨论了该方法的好处和缺点,多数人认为不应该把 web 服务器放进内核中, 相反内核加入最小的钩子 hooks 来提高 web 服务器的性能,这样对其它形式的服务器就有益。 具体请看 Zach Brown的讨论 对比用户级别和内核的 http 服务器。 2.4 linux 内核中为用户程序提供了足够的权力( power ),就像 X15 服务器运行的速度和 TUX 几乎一样,但是它没有对内核做任何改变。
注释
Richard Gooch 曾经写了一篇讨论 I/O选项 的论文。
2001, Tim Brecht MMichal Ostrowski 为使用简单的基于 select 的服务器 做了各种策略的测 试。 测试的数据值得看一看。
2003, Tim Brecht 发表了 userver的源码 , web 服务器是整合了 Abhishek Chandra, David Mosberger, David Pariag Michal Ostrowski 所写的几个服务器, 可以使用 select(), poll(), epoll() sigio.
早在 1999.3, Dean Gaudet就表示 :
我一直在问 为什么你们不使用基于 select/event Zeus 的模型,它明显是最快的。 ”...
他们不使用它的原因可以简单归结为 太难理解了,并且其中的性能指标还不清楚 ,但是几个月后,当该模型变得易懂时人们就开始愿意使用它了。
Mark Russinovich 写了 一篇评论 文章 讨论了在的 linux 2.2 内核上的 I/O 策略问题。 尽管某些地方似乎有点错误,不过还是值得去看。特别是他认为 Linux2.2 的异步 I/O ( 请看上面的 F_SETSIG) 并没有在数据准备好时通知用户进程,而只有在新的连接到达时才有。 这看起来是一个奇怪的误解。 还可以看看 早期的一些 comments , Ingo Molnar 1999.4.30所举的反例 , Russinovich 1999.5.2 comments , Alan Cox 反例 ,和各种 linux内核邮件 . 我怀疑他想说的是 Linux 不支持异步磁盘 I/O ,这在过去是正确的,但是现在 SGI 已经实现了 KAIO ,它已不再正确了。
查看页面 sysinternals.com MSDN 了解一下 完成端口 据说它是 NT 中独特的技术, 简单说, win32 " 重叠 I/O" 被认为是太低水平而不方面使用, 完成端口 是提供了完成事件队列的封装,再加上魔法般的调度, 通过允许更多的线程来获得完成事件如果该端口上的其它已获得完成事件的线程处于睡眠中时(可能正在处理阻塞 I/O ),从而可以保持运行线程数目恒定( scheduling magic that tries to keep the number of running threads constant by allowing more threads to pick up completion events if other threads that had picked up completion events from this port are sleeping (perhaps doing blocking I/O).
查看 OS/400 I/O完成端口支持 .
1999.9 ,在 linux 内核邮件列表上曾有 一次非常有趣的讨论,讨论题目为 "15,000 个并发连接 " ( 并且延续到 第二周 ). Highlights:
·                     Ed Hall 发表了 一些他自己的经验:他已经在运行 Solaris UP P2/333 上完成 >1000 个连接每秒。 他的代码使用了一个很小的线程池(每个 cpu 1 或者 2 个线程池),每个线程池使用事件模型来管理大量的客户端连接。
·                     Mike Jagdis 发表了一份关于 poll/select开销的分析 , 他表示“目前的 select/poll 实现还可以进行大幅度的性能提升,特别是在阻塞的环境下,但是随着文件描述符的增多 epoll/select 的开销也会同步增长,因为 epoll/select 没有,也不能够记住它感兴趣的描述符。但这很容易通过一个新的 API 来修正。随时欢迎任何建议
·                     Mike 发表了 为提高 select() poll()的性能所做的工作 .
·                     Mike posted a bit about a possible API to replace poll()/select() : "How about a 'device like' API where you write 'pollfd like' structs, the 'device' listens for events and delivers 'pollfd like' structs representing them when you read it? ... "
·                     Rogier Wolff suggested using "the API that the digital guys suggested", http://www.cs.rice.edu/~gaurav/papers/usenix99.ps
·                     Joerg Pommnitz pointed out that any new API along these lines should be able to wait for not just file descriptor events, but also signals and maybe SYSV-IPC. Our synchronization primitives should certainly be able to do what Win32's WaitForMultipleObjects can, at least.
·                     Stephen Tweedie asserted that the combination of F_SETSIG, queued realtime signals, and sigwaitinfo() was a superset of the API proposed in http://www.cs.rice.edu/~gaurav/papers/usenix99.ps. He also mentions that you keep the signal blocked at all times if you're interested in performance; instead of the signal being delivered asynchronously, the process grabs the next one from the queue with sigwaitinfo().
·                     Jayson Nordwick F_SETSIG 同步事件模型和完成端口进行了 分析比较 ,总结出二者非常相似。
·                     Alan Cox 表示 that an older rev of SCT's SIGIO patch is included in 2.3.18ac.
·                     Jordan Mendelson 发表了 一些示例代码来展示如何使用 F_SETSIG.
·                     Stephen C. Tweedie continued the comparison of completion ports and F_SETSIG, and noted: "With a signal dequeuing mechanism, your application is going to get signals destined for various library components if libraries are using the same mechanism," but the library can set up its own signal handler, so this shouldn't affect the program (much).
·                     Doug Royer 表示他在 Solaris2.6 上实现了 10 万个连接。他工作在 Sun calendar server 上。 noted that he'd gotten 100,000 connections on Solaris 2.6 while he was working on the Sun calendar server. Others chimed in with estimates of how much RAM that would require on Linux, and what bottlenecks would be hit.
Interesting reading!
 
在打开文件句柄数目上的限制
·                     任何一种 Unix: 限制可以通过 ulimit setrlimit 来设定 .
·                     Solaris: the Solaris FAQ, question 3.46 (or thereabouts; 他们会周期性的对问题重新编号 ).
·                     FreeBSD:
·                      
·                     编辑文件 /boot/loader.conf, 增加一行:
set kern.maxfiles=XXXX
其中 XXXX 是系统想要达到关于文件描述符上的极限。重启系统生效 . 感谢一位不知名的读者,他在信中声称,他在 FreeBSD 4.3 上做到了远超过 10000 个接连数,他说,
"FWIW: 实际你不能只是简单的通过 sysctl 调整 FreeBSD 的最大连接数, .... 你得通过修改 /boot/loader.conf 才可以 .
The reason for 原因是初始化 sockets tcpcb 结构体的 zalloci() 调用早在系统启动期就发生。
this is that the zalloci() calls for initializing the sockets and tcpcb structures zones occurs very early in system startup, in order that the zone be both type stable and that it be swappable.
You will also need to set the number of mbufs much higher, since you will (on an unmodified kernel) chew up one mbuf per connection for tcptempl structures, which are used to implement keepalive."
另一位读者称:
" 至于在 FreeBSD 4.4, 结构体 tcptempl 将不再保留 ; 你不必再担心一个 mbuf 被破坏了。 you no longer have to worry about one mbuf being chewed up per connection."
其它参见 :
o                  the FreeBSD 手册
o                  SYSCTL TUNING , LOADER TUNABLES , and KERNEL CONFIG TUNING in 'man tuning'
o                  The Effects of Tuning a FreeBSD 4.3 Box for High Performance , Daemon News, Aug 2001
o                  postfix.org tuning notes , covering FreeBSD 4.2 and 4.4
o                  the Measurement Factory's notes , circa FreeBSD 4.3
·                     OpenBSD: 一位读者称:
"In OpenBSD, an additional tweak is required to increase the number of open filehandles available per process: the openfiles-cur parameter in /etc/login.conf needs to be increased. You can change kern.maxfiles either with sysctl -w or in sysctl.conf but it has no effect. This matters because as shipped, the login.conf limits are a quite low 64 for nonprivileged processes, 128 for privileged."
·                     Linux: 参见 Bodo Bauer's /proc 文档 . 2.4 kernels 内核上 :
通过命令: echo 32768 > /proc/sys/fs/file-max 
增加了系统限制的可打开文件描述符的数目。另外,命令
ulimit -n 32768
增加了当前进程的可打开文件描述符的数目。
2.2.x 内核上 , 通过命令
echo 32768 > /proc/sys/fs/file-max echo 65536 > /proc/sys/fs/inode-max
增加了系统限制的可打开文件描述符的数目。 , 使用命令:
ulimit -n 32768
增加了当前进程的可打开文件描述符的数目为 32768
我查证过,通过上面这种方式 ,在 Red Hat 6.0 上一个进程可打开至少 31000 个文件描述符。另一个伙伴通过同样的方式验证了,在 2.2.12 核心上,至少可以打开 9 万个文件描述符。上限看起来取决于可用内存。
Stephen C. Tweedie about how to set ulimit limits globally or per-user at boot time using initscript and pam_limit.
在较老的 2.2 内核上 , 尽管可以使用上述命令去修改相关的内核参数,但每个进程中可打开的文件数目依然限制在 1024 .
也可查看 Oskar's 1998 post , 其中 谈到了在 2.0.36 内核中,在文件描述符的问题上,每进程和系统范围内对此的限制。
线程上的限制
在任何构架上,你可能需要减少分配给每一个线程的堆栈空间以避免耗尽虚拟地址空间。如果你使用 pthreads 库,你可以用 pthread_attr_init() 在运行期设定线程堆栈大小。
·                     Solaris: 有多少内存就支持多少线程。仅道听途说。
·                     带有 NPTL[ 译注 1] Linux 2.6 内核 : /proc/sys/vm/max_map_count 如果需要的话,可以增加到大约 32000 个线程。 ( 如果你要使用这么多的线程,你需要使线程的堆栈小一点。或者,除非你用 64 位的处理器。 ) 更多信息请参见 NPTL 邮件列表 , 比如,有关线程的主题 "不能再创建更多的 32K线程了吗?
[ 译注 1 :在 GNU/Linux 操作系统中, Native POSIX Thread Library (NPTL) 是一种软件特性,它能够使遵循 POSIX Threads 规范的程序在 Linux 内核上高效的运行。 ]
·                     Linux 2.4: /proc/sys/kernel/threads-max 指出了系统的最大线程数目。在我的 Red Hat 8 操作系统上缺省是 2047 . 最常见的方式,你可以增加这个值通过 echo 把这个新值传到文件,比如: "echo 4000 > /proc/sys/kernel/threads-max"
·                     Linux 2.2: 甚至 2.2.13 版本的内核也限制了线程的数量。至少 Intel 是如此 . 我不知道其它 CPU 架构上的限制。 Mingo Intel平台上的 2.1.131版本发布的补丁( patch for 2.1.131 on Intel )去除了这个限制 . 好像集成到 2.3.20 版本中了 .
其它的参看 Volano's detailed instructions for raising file, thread, and FD_SET limits in the 2.2 kernel . 哦, 这个重要的文档帮你搞定了很多你自己难以搞定的事情。但就是有点过时了。
·                     Java: 参见 Volano 详细的基准测试报告( Volano's detailed benchmark info , 还有他们的 info on how to tune various systems to handle lots of threads.
Java 的议题
Up through JDK 1.3, Java's standard networking libraries mostly offered the one-thread-per-client model . There was a way to do nonblocking reads, but no way to do nonblocking writes.
In May 2001, JDK 1.4 introduced the package java.nio to provide full support for nonblocking I/O (and some other goodies). See the release notes for some caveats. Try it out and give Sun feedback!
HP's java also includes a Thread Polling API .
In 2000, Matt Welsh implemented nonblocking sockets for Java; his performance benchmarks show that they have advantages over blocking sockets in servers handling many (up to 10000) connections. His class library is called java-nbio ; it's part of the Sandstorm project. Benchmarks showing performance with 10000 connections are available.
See also Dean Gaudet's essay on the subject of Java, network I/O, and threads, and the paper by Matt Welsh on events vs. worker threads.
Before NIO, there were several proposals for improving Java's networking APIs:
·                     Matt Welsh's Jaguar system proposes preserialized objects, new Java bytecodes, and memory management changes to allow the use of asynchronous I/O with Java.
·                     Interfacing Java to the Virtual Interface Architecture , by C-C. Chang and T. von Eicken, proposes memory management changes to allow the use of asynchronous I/O with Java.
·                     JSR-51 was the Sun project that came up with the java.nio package. Matt Welsh participated (who says Sun doesn't listen?).
其它的提示
·                     零拷贝技术( Zero-Copy [ 译注 2]
·                     正常的情况下,数据从此处到彼处需要被拷贝很多次。任何旨在消除这些拷贝使之减少到物理最小量拷贝的技术被称之为“零拷贝技术”。
 [ 译注 2:零拷贝技术分为两步:
1、硬件到内核,实现的前提是网卡必须支持 DMA,对于不支持DMA的网卡无法实现零拷贝。
2、内核到用户层,将系统内核中存储数据报的内存区域映射到检测程序的应用程序空间或者在用户空间建立一缓存,并将其映射到内核空间。
]
 
o                  Linux 内核 2.4.17-2.4.20 下, Thomas Ogrisegg's zero-copy send patch 为内存映射文件所打的补丁, 声称快过 sendfile().
o                  IO-Lite 是关于一组 I/O 原语的方案,它省去了许多不必要的拷贝。
o                  早在 1999 Alan Cox提出零拷贝( zero-copy)有时不值得如此麻烦 ( 虽然他也很喜欢 sendfile().)
o                  2000 7 月的时候, Ingo 2.4 内核上为 TUX 实现了一种 TCP 的零拷贝implemented a form of zero-copy TCP , 并且他声称他接着会在用户空间上实现了这一功能。 .
o                  Drew Gallatin Robert Picco 增加了一些零拷贝的特性到 FreeBSD ; 如果你在一个套接字上调用 write() 或者 read() ,指针是按页对齐的,发送的数据的总量至少为一页,传输的数据量至少是一页, page-aligned the idea seems to be that if you call write() or read() on a socket, the pointer is page-aligned, and the amount of data transferred is at least a page, *and* you don't immediately reuse the buffer, memory management tricks will be used to avoid copies. But see followups to this message on linux-kernel for people's misgivings about the speed of those memory management tricks.
根据 Noriyuki Soda 的说法 :
发送方的零拷贝技术自从 NetBSD-1.6 发行版本就得到了支持。你需要设定内核参数“ SOSEND_LOAN ”。这个参数是目前所有 NetBSD 的缺省参数。(你可以在内核选项中通过设定 "SOSEND_NO_LOAN" 取消这个特性)。在这个特性的支持下,当超过 4K 的数据被发送时,零拷贝技术自动生效。
 
o                  The sendfile() system call can implement zero-copy networking.
o                  sendfile() 系统调用能够实现网络应用中的零拷贝。在 Linux FreeBSD sendfile() 函数允许你告诉内核发送一个文件的部分或全部。内核会尽可能有效的完成操作。这个调用在使用多线程的服务器上或使用非阻塞 IO 的服务器上也同样工作得很好。 ( Linux 平台上 , 目前文档并不完善 ; use _syscall4 to call it . Andi Kleen 正在写一个新的使用文档来包含这个主题。参见 Jeff Tranter Exploring The sendfile System Call in Linux Gazette issue 91.) Rumor has it , ftp.cdrom.com sendfile() 中显著的受益。
linux2.4 内核上正在实现一个 sendfile() 的零拷贝方案。参见 LWN Jan 25 2001 .
一个开发者使用了 Freebsd 下的 sendfile() ,他反馈说,使用 POLLWRBAND 而不是 POLLOUT 造成很大的(性能)差异。
Solaris 8 (as of the July 2001 update) 下有一个新的系统调用 'sendfilev' 一个使用文档在这里…… copy of the man page is here. 8 07 1 月的发布要点 ( release notes ) 也提到了它。我猜想这个调用在阻塞模式下发送(数据)到一个套接字最为有用。使用非阻塞套接字会有点麻烦。 . Solaris
Solaris 8 (as of the July 2001 update) has a new system call 'sendfilev'. A copy of the man page is here. . The Solaris 8 7/01 release notes also mention it. I suspect that this will be most useful when sending to a socket in blocking mode; it'd be a bit of a pain to use with a nonblocking socket.
  • 使用 writev (或 TCP_CORK )来避免小帧   Avoid small frames by using writev (or TCP_CORK)
TCP_CORK 是一个在 linux 下的套接字的新的选项,它告诉内核避免发送个别的帧,当有很多小的 write() 调用,而因为某些原因你又不能将它们捆绑在一起,在这种情况下 TCP_CORK 会非常有作用。不设定这个选项,而是刷新缓存。更好点是使用 writev() 。。。。。。
 
参见 LWN Jan 25 2001 摘要信息,关于 linux 内核中的 TCP_CORK, 还有另个一个可能的选择 MSG_MORE, 里面其中有一些有趣的讨论 .
  • Behave sensibly on overload. 在超负荷状态下智能感知
  • [Provos, Lever, and Tweedie 2000 ] notes that dropping incoming connections when the server is overloaded improved the shape of the performance curve, and reduced the overall error rate. They used a smoothed version of "number of clients with I/O ready" as a measure of overload. This technique should be easily applicable to servers written with select, poll, or any system call that returns a count of readiness events per call (e.g. /dev/poll or sigtimedwait4()).
  • Some programs can benefit from using non-Posix threads. 使用非 Posix 线程令一些程序受益
  • Not all threads are created equal. The clone() function in Linux (and its friends in other operating systems) lets you create a thread that has its own current working directory, for instance, which can be very helpful when implementing an ftp server. See Hoser FTPd for an example of the use of native threads rather than pthreads.
  • Caching your own data can sometimes be a win. 缓存你自己的数据有时好处很大
  • "Re: fix for hybrid server problems" by Vivek Sadananda Pai ([email protected]) on new-httpd , May 9th, states:
"I've compared the raw performance of a select-based server with a multiple-process server on both FreeBSD and Solaris/x86. On microbenchmarks, there's only a marginal difference in performance stemming from the software architecture. The big performance win for select-based servers stems from doing application-level caching. While multiple-process servers can do it at a higher cost, it's harder to get the same benefits on real workloads (vs microbenchmarks). I'll be presenting those measurements as part of a paper that'll appear at the next Usenix conference. If you've got postscript, the paper is available at http://www.cs.rice.edu/~vivek/flash99/ "
 
Other lim its 其它限制
  • 老的系统库使用 16 位的变量来保存文件句柄,当有超过 32767 个句柄时,这就会引起麻烦。 glibc2.1 应该没有这个问题 .
  • 许多系统使用 16 位变量来保存进程或线程 ID It would be interesting to port the Volano scalability benchmark to C, and see what the upper limit on number of threads is for the various operating systems.
  • 太多的线程局部内存被某些操作系统事件分配好,如果每个线程分配 1MB ,而总共的虚拟内存空间才 2GB ,这会造成只能生成 2000 个线程的上限。
  • 参看这个页面最后的性能比较图。 http://www.acme.com/software/thttpd/benchmarks.html . Notice how various servers have trouble above 128 connections, even on Solaris 2.6? 如果有人想出为什么,请告诉我。
  • 注意 : 如果 TCP 栈存在一个 bug ,而引起在 SYN FIN 时间上的小小的延时( 200ms , 这在 Linux 2.2.0-2.2.6   中存在的,操作系统或 http 后台程序在打开的连接数上有一个硬性限制, if the TCP stack has a bug that causes a short (200ms) delay at SYN or FIN time, as Linux 2.2.0-2.2.6 had, and the OS or http daemon has a hard limit on the number of connections open, you would expect exactly this behavior. There may be other causes.
核心问题
Linux 来说, 核心的瓶颈正不断的被突破。可以查看 Linux Weekly News , Kernel Traffic , the Linux-Kernel mailing list , my Mindcraft Redux page .
1999 3 月,微软主办了一次基准测试来比较 NT Linux ,比较他们在可服务的 http smb 客户的最大数量上面的性能。结果显示 Linux 性能不佳。更多的信息可以参考我的文章: my article on Mindcraft's April 1999 Benchmarks
See also The Linux Scalability Project . They're doing interesting work, including Niels Provos' hinting poll patch , and some work on the thundering herd problem .
See also Mike Jagdis' work on improving select() and poll() ; here's Mike's post about it.
Mohit Aron ([email protected]) writes that rate-based clocking in TCP can improve HTTP response time over 'slow' connections by 80%.
 
测试服务器性能
两种测试很简单、也有趣但同时也很难。
  1. 每秒钟原始连接数。 raw connections per second ( 你可以在一秒钟内处理多少个 512 字节的文件呢? )
  2. total transfer rate on large files with many slow clients (how many 28.8k modem clients can simultaneously download from your server before performance goes to pot?)
Jef Poskanzer 发表了许多关于比较 web 服务器性能的基准测试。 . 参看 http://www.acme.com/software/thttpd/benchmarks.html for his results.
I also have a few old notes about comparing thttpd to Apache that may be of interest to beginners.
Chuck Lever keeps reminding us about Banga and Druschel's paper on web server benchmarking . It's worth a read.
IBM has an excellent paper titled Java server benchmarks [Baylor et al, 2000]. It's worth a read.
Examples
值得关注的基于 select() 的服务器
  • thttpd 非常简单 . 它使用单进程,也有很好的性能表现,但性能不能随着 CPU 的数目扩展。也可以使用 kqueue.
  • mathopd . 类似于 thttpd.
  • fhttpd
  • boa
  • Roxen
  • Zeus , 一个商业的服务器,试图做到的绝对的快。参见他们的 tuning guide .
  • 其它的非 Java 服务器列表在 http://www.acme.com/software/thttpd/benchmarks.html
  • BetaFTPd
  • Flash-Lite – 使用 IO-Lite web 服务器 .
  • Flash:一个高效的和可移植的 Web服务器 使用 select(), mmap(), mincore()
  • The Flash web server as of 2003 -- uses select(), modified sendfile(), async open()
  • xitami – 没有使用线程。为了系统的可移植性,使用 select() 来实现自己的线程抽象。
  • Medusa - a server-writing toolkit in Python that tries to deliver very high performance.
  • userver – 一个小型的 http 服务器可以使用 select, poll, epoll, 或者 sigio
值得关注的基于 /dev/poll 的服务器
  • N. Provos , C. Lever , "Scalable Network I/O in Linux," May, 2000. [FREENIX track, Proc. USENIX 2000, San Diego, California (June, 2000).] Describes a version of thttpd modified to support /dev/poll. Performance is compared with phhttpd.
值得关注的基于 kqueue() 的服务器
  • thttpd (as of version 2.21?)
  • Adrian Chadd says "I'm doing a lot of work to make squid actually LIKE a kqueue IO system"; it's an official Squid subproject; see http://squid.sourceforge.net/projects.html#commloops . (This is apparently newer than Benno 's patch .)
值得关注的基于实时信号的服务器
  • Chromium's X15. This uses the 2.4 kernel's SIGIO feature together with sendfile() and TCP_CORK, and reportedly achieves higher speed than even TUX. The source is available under a community source (not open source) license. See the original announcement by Fabio Riccardi.
  • Zach Brown's phhttpd - "a quick web server that was written to showcase the sigio/siginfo event model. consider this code highly experimental and yourself highly mental if you try and use it in a production environment." Uses the siginfo features of 2.3.21 or later, and includes the needed patches for earlier kernels. Rumored to be even faster than khttpd. See his post of 31 May 1999 for some notes.
值得关注的基于线程的服务器
  • Hoser FTPD . See their benchmark page .
  • Peter Eriksson's phttpd and
  • pftpd
  • The Java-based servers listed at http://www.acme.com/software/thttpd/benchmarks.html
  • Sun's Java Web Server (which has been reported to handle 500 simultaneous clients )
值得关注的基于内核中的服务器
  • khttpd
  • "TUX" (Threaded linUX webserver) by Ingo Molnar et al. For 2.4 kernel.
其它值得关注的链接
  • Jeff Darcy's notes on high-performance server design
  • Ericsson's ARIES project -- benchmark results for Apache 1 vs. Apache 2 vs. Tomcat on 1 to 12 processors
  • Prof. Peter Ladkin's Web Server Performance page.
  • Novell's FastCache -- claims 10000 hits per second. Quite the pretty performance graph.
  • Rik van Riel's Linux Performance Tuning site

你可能感兴趣的:(c,linux,Solaris,FreeBSD,asynchronous,linux内核)