最近看了一下Unix网络编程相关的内容,而后发现了一篇很是经典的文章, 内容可能不是很新,不过真的很经典,C10K问题,简单翻译了一下(markdown转换过来格式可能存在少许问题)对掌握linux io和 linux 线程会有更深的理解.(文内存在大量的连接.) 原文连接 The C10K problemphp
如今 web 服务器须要同时处理上万请求,难道不是吗?毕竟现在的网络将会有很大的发展空间. 计算机也一样强大.你能够花1200美圆买一台 1000MHz,2G 内存和1000Mbits/sec的网卡的机器.让咱们来看看-- 20000 客户端,每一个客户端 50KHz, 1000Kb 和每秒 50Kb,那没有什么比这两万个客户端每一个每秒从磁盘中取出4千字节并将它们每秒发送到网络上去更消耗资源了.(顺便说一下,每一个客户端0.0.8美圆,一些操做系统收费的单个客户端 美圆的许可费看起来有点贵)因此硬件再也不是一种瓶颈.css
在1999年,最繁忙的 ftp 网站之一, cdrom.com, 实际上经过一个千兆以太网网卡同时处理 10000个客户端.如今相同的速度也被ISP 提供,他们但愿它变得愈来愈受大型企业客户的欢迎.html
轻量级的客户端计算模型彷佛又开始变得流行起来了 - 服务器在互联网上运行,为数千个客户提供服务.前端
基于以上的一些考虑,这有一些关于如何配置操做系统或者编写支持数千客户端的代码问题提出了一些注意点. 讨论的中心的主要是围绕着类Unix操做系统,由于这是我我的感兴趣的领域,可是Windows也会涉及一点.java
参阅下 Nick Black 的杰出的 快速的Unix服务器网页,了解大约2009的状况.node
在2003年10月,Felix von Leitner 整理了一个优秀的关于网络可扩展性的网站和演示,完成了多种不一样的网络系统调用和操做系统的性能比较.其中一个发现是 linux 2.6 内核击败了 2.4 内核,固然这里有不少很好的图片会让操做系统开发人员在平时提供点想法.linux
若是你没有阅读过the late W. Richard Stevens的Unix网络编程: 网络Apis:套接字和Xti(第1卷)的拷贝,请尽快获取一份,它描述了不少的于 I/O 策略和编写高性能服务器的陷阱.它甚至谈到了 'thundering herd'问题.当你在阅读它时,请阅读 Jeff Darcy写的关于高性能服务器设计.nginx
(另一本书构建可扩展的网站可能会对使用而不是编写一个web服务器的人会有帮助)git
如下提供了几个预打包的库,它们抽象了下面介绍的一些技术,使代码与操做系统隔离,并使其更具可移植性.github
网络软件的设计者有多种选择.这有一些:
下边的5中组合彷佛很是流行:
... 在全部的网络句柄上都设置为非阻塞模式,使用 select() 或则 poll() 去告知哪一个网络句柄处理有数据等待.此模型是最传统的.这种模式下,内核告诉你是否一个文件描述符就绪,自从上次内核告诉你它以来,你是否对该文件描述符作了任何事情.('水平触发'这个名词来自计算机硬件设计;它与'边缘触发'相反).Jonathon Lemon在他的关于BSDCON 2000 paper kqueue()的论文中介绍了这些术语
注意: 牢记来自内核的就绪通知只是一个提示,这一点尤其重要;当你尝试去读取文件描述符的时候,它可能没有就绪.这就是为何须要在使用就绪通知时使用非阻塞模式的缘由.
一个重要的瓶颈是 read()或 sendfile() 从磁盘块读取时,若是该页当前并不在内存中.在设置非阻塞模式的磁盘文件处理是没有影响的.内存映射磁盘文件也是如此.首先一个服务须要磁盘 I/O时,他的处理块,全部客户端必须等待,所以原生非线程性能将会被浪费了.
这也就是异步 I/O 的目的,固然仅限于没有 AIO 的系统上,用多线程和多进程进行磁盘 I/O 也可能解决这个瓶颈.一种方法是使用内存映射文件,若是 mincore() 表示须要 I/O,让一个工做线程去进行 I/O 操做,并继续处理网络流量.Jef Poskanzer 提到 Pai, Druschel, and Zwaenepoel的1999 Flash web服务器使用这个技巧;他们在Usenix '99发表了关于它的演讲.看起来 mincore() 在BSD-derived Unixes 上是可用的,如像FreeBSD和Solaris,但它不是单Unix规范的一部分.从kernel2.3.51 开始,它也开始是linux的一部分,感谢Chuck Lever.
可是在2003年十一月的 freebsd-hackers list, Vivek Pei 等人报道了使用他们的 Flash web服务器有一个很好的结果.而后在攻击其瓶颈,其中发现一个瓶颈是 mincore(猜想以后这不是一个好办法),另一个就是 sendfile 阻塞磁盘访问;他们一种修改的 sendfile(),当他的访问磁盘页还没有处于核心状态时返回相似 EWOULDBLOCK 的内容,提高了性能.(不知道怎么告诉用户页如今是常驻的...在我看来真正须要的是aio_sendfile().)他们优化的最终结果是在 1GHZ/1GB 的FreeBSD盒子上 SpecWeb99 得分约为800,这比spec.org上的任何文件都要好.
在非阻塞套接字的集合中,关于单一线程如何告知哪一个套接字是准备就绪的,列出了几种方法:
就绪改变通知(或边缘就绪通知)意味着你向内核提供文件描述符,而后,当该描述符从 not ready 转换为 ready 时,内核会以某种方式通知你.而后它假定你已知文件描述符已准备好,同时不会再对该描述符发送相似的就绪通知,直到你在描述符上进行一些操做使得该描述符再也不就绪(例如,直到你收到 EWOULDBLOCK 错误为止)发送,接收或接受呼叫,或小于请求的字节数的发送或接收传输).
当你使用就绪改变通知时,你必须准备处理好虚假事件,由于最多见的实现是只要接收到任何数据包都发出就绪信号,而无论文件描述符是否准备就绪.
这与"水平触发"就绪通知相反.它对编程错误的宽容度要低一些,由于若是你只错过一个事件,那么事件的链接就会永远停滞不前.能够尽管如此,我发现边缘触发的就绪通知能让使用OpenSSL编程非阻塞客户端变得更容易,所以仍是值得尝试.
[Banga, Mogul, Drusha '99]在1999年描述了这种类型的模式.
有几种API使应用程序检索"文件描述符准备就绪"通知:
/* 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);复制代码
每一个fd一个信号
Signal-per-fd是由Chandra和Mosberger提出的对实时信号的一种改进,它经过合并冗余事件来减小或消除实时信号队列溢出.但它并无超越 epoll.他们的论文 (www.hpl.hp.com/techreports…)将此方案的性能与select() 和 /dev/poll 进行了比较.
Vitaly Luban于2001年5月18日宣布了一项实施该计划的补丁;他的补丁产生于www.luban.org/GPL/gpl.htm….(注意:截至2001年9月,这个补丁在高负载下可能存在稳定性问题.dkftpbench在大约4500个用户可能会触发oops.)
参阅Poller_sigfd (cc,h)是一个如何使用 signal-per-fd 与其余就绪通知模式交互的示例.
这在Unix至今都没有流行起来,多是由于较少的操做系统支持了异步 I/O,也多是由于(像非阻塞 I/O)它要求从新思考应用程序.在标准 Unix 下,异步 I/O 被aio_ 接口提供(从该连接向下滚动到"异步输入和输出"),它将信号和值与每一个 I/O操做相关联.信号及其值排队并有效地传递给用户进程.这是来自 POSIX 1003.1b 实时扩展,也是单Unix规范第二版本.
AIO一般与边缘触发完成通知一块儿使用,即当操做完成时,信号排队.(它也能够经过调用aio_suspend()与水平触发的完成通知一块儿使用,虽然我怀疑不多有人这样作.)
glibc 2.1和后续版本提供了一个普通的实现,仅仅是为了兼容标准,而不是为了得到性能上的提升.
截止linux内核 2.5.32,Ben LaHaise的 Linux AIO 实现已合并到主 Linux 内核中.它不使用内核线程,同时还具备很是高效的底层api,可是(从2.6.0-test2开始)还不支持套接字.(2.4内核还有一个 AIO 补丁,但 2.5/2.6 实现有些不一样.)更多信息:
Suparna还建议看看DAFS API 对 AIO 的方法.
Red Hat 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: 真实世界的编程听说涵盖了对aio的一个很好的介绍.
Solaris早期非标准的aio实现的教程在线Sunsite.这可能值得一看,但请记住,你须要在精神上将"aioread"转换为"aio_read"等.
请注意,AIO不提供在不阻塞磁盘 I/O 的状况下打开文件的方法; 若是你关心打开磁盘文件致使休眠,Linus建议你只需在另外一个线程中执行 open()而不是是进行 aio_open() 系统调用.
在Windows下,异步 I/O 与术语"重叠 I/O "和 IOCP 或"I/O完成端口"相关联.微软的 IOCP 结合了现有技术的技术,如异步 I/O(如aio_write)和排队完成通知(如将 aio_sigevent 字段与 aio_write 一块儿使用时),以及阻止某些请求尝试保持运行线程数量相关的新想法具备单个 IOCP 常量.欲得到更多信息,请参阅 sysinternals.com 上的 Mark Russinovich 撰写的I/O 完成端口的内部,Jeffrey Richter的书 "为Microsoft Windows 2000编写服务端程序"(Amazon, MSPress), U.S. patent #06223207, 或者MSDN.
...让 read() 和 write() 阻塞.每一个客户端使用整个栈侦会有很大的缺点,就是消耗内存.不少操做系统也难以操做处理上百个线程.若是每一个线程得到2MB堆栈(不是很是见的默认值),则在 32 位机器上的 (2^30/2 ^21)= 512 个线程上会耗尽虚拟内存,具备 1GB 用户可访问的VM(好比,Linux 一般在 x86 上容许)你能够经过提供更小的栈解决这个问题,可是线程一旦建立,大多数线程库都不容许增长线程栈,因此这样作就意味着你必须使你的程序最小程度地使用内存.你也能够经过转移到64位处理器来解决这个问题.
在Linux, FreeBSD, Solaris上的线程支持是正在完善,即便对于主流用户来讲,64位处理器也即将到来.也许在不就的未来,那些喜爱每一个客户端使用一个线程的人也有能力服务10000个客户端了.然而,在目前这个时候,若是你真的想要支持那么多客户,你可能最好仍是使用其余一些方法.
对于绝不掩饰的亲线程观点的人,请参阅为何事件是一个坏主意(对于高并发服务器)由von Behren,Condit和Brewer,UCB,在HotOS IX上发布.有反线营地的任何人能指出一篇反驳这篇论文的论文吗?:-)
Linux线程是标准Linux线程库的名称.从 glibc2.0 开始,它就集成到 glibc 中,主要是符合 Posix 标准,但性能和信号支持度上都不尽如人意.
NGPT是 IBM 启动的为 Linux 带来更好的 Posix 线程兼容性的项目.他目前的稳定版本是2.2,工做的很是好...可是 NGPT 团队宣布他们将 NGPT 代码库置于support-only模式,由于他们以为这是"长期支持社区的最佳方式". NGPT团队将会继续改进 Linux 的线程支持,可是如今主要集中在NPTL.(感谢NGPT团队的出色工做以及他们以优雅的方式转向NPTL.)
NPTL是由Ulrich Drepper(glibc的维护人员)和Ulrich Drepper发起的,目的是为Linux带来的world-class Posix线程库支持.
截至2003年10月5日,NPTL 如今做为附加目录合并到 glibc cvs 树中(就像linux线程),因此它几乎确定会与 glibc 的下一个版本一块儿发布.
Red Hat 9是最先的包含NPTL的发行版本(这对某些用户来讲有点不方便,但有人不得不打破僵局...)
NPTL 连接:
这是我尝试写的描述NPTL历史的文章(也能够看看Jerry Cooperstein的文章):
在2002年3月, NGPT团队的Bill Abt, glibc的维护者与Ulrich Drepper和其余人会面探讨LinuxThreads的发展.会议产生的一个想法是提升互斥性能;Rusty Russell 等人后来实现了快速用户空间锁(futexes)),它如今被用在 NGPT 和 NPTL 中.大多数与会者认为NGPT应该被合并到glibc.
但Ulrich Drepper并不喜欢 NGPT,认为他能够作得更好.(对于那些试图为 glibc 作出补丁的人来讲,这可能不会让人大吃一惊:-)在接下来的几个月里,Ulrich Drepper,Ingo Molnar致力于 glibc 和内核的变化,这些变化构成了 Native Posix线程库(NPTL).NPTL使用了NGPT设计的全部内核改进,并利用一些新功能:
> NPTL使用NGPT引入的三个内核特性:getpid()返回 PID,CLONE_THREAD 和 futexes;NPTL还使用(并依赖)更普遍的新内核功能,做为该项目的一部分开发.
> 引入 2.5.8 内核的 NGPT 中的一些项目获得了修改,清理和扩展,例如线程组处理(CLONE_THREAD).[影响 NGPT 兼容性的 CLONE_THREAD 更改与 NGPT 人员同步,以确保NGPT不会以任何不可接受的方式破坏.]
> NPTL开发和使用的内核功能在设计白皮书中有描述,people.redhat.com/drepper/npt… ...
> 简短列表:TLS支持,各类克隆扩展(CLONE_SETTLS,CLONE_SETTID,CLONE_CLEARTID),POSIX线程信号处理,sys_exit()扩展(在VM发布时发布TID futex)sys_exit_group()系统调用,sys_execve()加强功能 并支持分离的线程.
> 还有扩展 PID 空间的工做 - 例如,procfs因为 64K PID 的设计,为max_pid 和 pid 分配可伸缩性的工做而崩溃.此外,还进行了许多仅针对性能的改进.
> 本质上,新功能彻底是使用1:1线程方法 - 内核如今能够帮助改进线程的全部内容,而且咱们为每一个基本线程原语精确地执行最低限度必需的上下文切换和内核调用.
FreeBSD线程支持
FreeBSD同时支持 linux 线程和用户空间线程库.此外,在 FreeBSD 5.0 中引入了一个名为 KSE 的 M:N 实现.概述,参阅www.unobvious.com/bsd/freebsd….
2003年3月25日,Jeff Roberson在 freebsd-arch 上发布了帖子:
...感谢Julian,David Xu,Mini,Dan Eischen,和其它的每一位参加了KSE和libpthread开发的成员所提供的基础,Mini和我已经开发出了一个 1:1 模型的线程实现.此代码与 KSE 并行工做,不会以任何方式更改它.它实际上有助于经过测试共享位来使M:N线程更接近...
并于2006年7月,Robert Watson提出的 1:1 线程应该成为FreeBsd 7.x中的默认实现:
我知道过去曾经讨论过这个问题,但我认为随着7.x向前推动,是时候从新考虑一下这个问题.在许多常见应用程序和特定场景的基准测试中,libthr 表现出比 libpthread 更好的性能... libthr也在咱们的大量平台上实现的,而且已经在几个平台上实现了 libpthread.咱们对 MySQL 和其余大量线程的使用者建议是"切换到libthr",这也是暗示性的! ...因此草书建议是:使libthr成为7.x上的默认线程库.
根据Noriyuki Soda的说明:
内核支持 M:N 基于 Scheduler Activations 模型线程库将于2003年1月18日合并到NetBSD-current中.
更多细节,看由NethanD系统公司的 Nathan J. Williams在2002年的FREENIX上的演示An Implementation of Scheduler Activations on the NetBSD Operating System.
Solaris中的线程支持发展...从 Solaris 2 到 Solaris 8,默认线程库使用 M:N 模型,但 Solaris 9 默认为 1:1 模型线程支持.看Sun的多线程编程指导和Sun关于 Java 和 Solaris 线程的笔记
众所周知,直到 JDK1.3.x 的 Java 不支持处理除每一个客户端一个线程以外的任何网络链接方法.Volanomark是一个很好的微基准测试,它能够在不一样数量链接中测量每秒消息的吞吐量.截至2003年5月,来自不一样供应商的 JDK 1.3实际上可以处理一万个同时链接 - 尽管性能显着降低.请参阅表4,了解哪些 JVM 能够处理10000个链接,以及随着链接数量的增长性能会受到影响.
在实现线程库时有一个选择: 你能够将全部线程支持放在内核中(这称为 1:1 线程模型),或者您能够将其中的至关一部分移动到用户空间(这称为 M:N 线程模型).有一点,M:N被认为是更高的性能,但它太复杂了,很难作到正确,大多数人都在远离它.
听说 Novell 和微软已经在不一样的时间作过这个,至少有一个 NFS 实现是这样作的,khttpd为Linux和静态网页作了这个,"TUX"(线程linux web服务器)是Ingo Molnar为Linux的一个快速且灵活的内核空间HTTP服务器. Ingo的2000年9月1日公告表示能够从ftp://ftp.redhat.com/pub/redhat/tux 下载 TUX 的alpha版本,并解释如何加入邮件列表以获取更多信息.
linux-kernel列表一直在讨论这种方法的优势和缺点,并且彷佛不是将 Web 服务器移动到内核中,内核应该添加最小的钩子来提升Web服务器的性能.这样,其余类型的服务器能够受益.参见例如Zach Brown的评论关于 userland 与内核 http 服务器的关系.彷佛2.4 linux 内核为用户程序提供了足够的功能,由于X15服务器的运行速度与Tux同样快,但不使用任何内核修改.
例如,参见netmap数据包 I/O 框架和Sandstorm基于这个概念验证Web服务器.
Richard Gooch已经写了一篇关于讨论 I/O 选项的论文.
在2001年,Tim Brecht和MMichal Ostrowski测试了多种策略为简化基于 select 的服务器.他们的数据值得一看.
在2003年,Tim Brecht发布了userver的源代码,由Abhishek Chandra, David Mosberger, David Pariag 和 Michal Ostrowski 编写的几台服务器组成的小型Web服务器.它能使用select(), poll(),或者sigio.
早在1999年3月,Dean Gaudet的文章:
我不断被问到"为何大家不使用像Zeus这样的基于select/event的模型?它显然是最快的."...
他的理由归结为"这真的很难,收益还不清楚",然而,在几个月内,很明显人们愿意继续努力.
Mark Russinovich 写了一篇社论和文章讨论在 linux内核2.2 中的 I/O 策略问题.值得一看,甚至他彷佛在某些方面也被误导了.特别是,他彷佛认为Linux 2.2 的异步 I/O (参见上面的F_SETSIG)在数据就绪时不会通知用户进程,只有当新链接到达时.这彷佛是一个奇怪的误解.也能够看看更早的草案,Ingo Molnar于1999年4月30日的反驳,Russinovich对1999年5月2日的评论, 一个来自Alan Cox的反驳,和各类linux-kernel的帖子,我怀疑他试图说 Linux 不支持异步磁盘I/O,这曾经是真的,可是如今 SGI 已经实现了KAIO,它再也不那么真实了.
有关"完成端口"的信息,请参阅sysinternals.com和MSDN上的这些网页,他说这是NT独有的;简而言之,win32的"重叠 I/O "结果过低而不方便,"完成端口"是一个提供完成事件队列的包装器,加上调试魔术试图保持运行的数量,若是从该端口获取完成事件的其余线程正在休眠(可能阻塞I/O)则容许更多线程获取完成事件,从而使线程保持不变。
1999年9月对linux-kernel进行了一次有趣的讨论"> 15,000个同时链接"(和线程的第二周).强调:
有趣的阅读!
set kern.maxfiles=XXXX
echo 32768 > /proc/sys/fs/file-max
ulimit -n 32768
ulimit -n 32768
echo 32768 > /proc/sys/fs/file-max echo 65536 > /proc/sys/fs/inode-max
ulimit -n 32768
ulimit -n 32768
在任何体系结构上,您可能须要减小为每一个线程分配的堆栈空间量,以免耗尽虚拟内存.若是使用pthreads,可使用pthread_attr_init() 在运行时设置它。
经过JDK 1.3, Java的标准网络库大多提供了一个客户端一个线程模型.这是一种非阻塞读的方式,可是没有办法去作非阻塞写.
在2001年5月. JDK 1.4 引进了包 java.nio 去提供彻底支持非阻塞 I/O (和其余好的东西).看发行说明警告.尝试一下,给Sun反馈!
HP 的 java 也包含了一个线程轮训API.
在2000, Matt Welsh为java实现了非阻塞套接字.他的性能基准测试显示他们优于在处理大量(大于10000)链接的服务器中的阻塞套接字.他的类库被称做java-nbio;他是Sandstorm项目的一部分.基准测试显示10000链接的性能是可用的.
参阅 Dean Gaude关于 Java , 网络 I/O, 和线程主题的文章,和 Matt Welsh 写的关于事件对比工做线程的论文
在 NIO 以前,有几个改进Java的网络API的建议:
注意: 若是TCP堆栈有一个bug,致使 SYN 或 FIN 时间更短(200ms)延迟,如 Linux 2.2.0-2.2.6 所示,而且操做系统或 http 守护程序对链接数有硬限制,你会期待这种行为.可能还有其余缘由.
对于Linux,看起来内核瓶颈正在不断修复.看Linux Weekly News,Kernel Traffic, the Linux-Kernel mailing list,和my Mindcraft Redux page.
1999年3月,微软赞助了一项比较 NT 和 Linux 的基准测试,用于服务大量的 http 和 smb客户端,linux的结果不如人意.另见关于Mindcraft 1999年4月基准测试的文章了解更多信息
另请参见Linux可扩展性项目.他们正在作有趣的工做.包括Niels Provos的暗示民意调查补丁,关于雷鸣般的群体问题的一些工做.
另请参与Mike Jagdis致力于改进 select() 和 poll();这是Mike关于它的帖子.
Mohit Aron(aron@cs.rice.edu)写道,TCP中基于速率的时钟能够将"缓慢"链接上的HTTP响应时间提升80%
特别是两个测试简单,有趣,并且很难:
Jef Poskanzer发布了比较许多Web服务器的基准测试.看他的结果www.acme.com/software/th…
我也有关于将thttpd与Apache比较的一些旧笔记可能对初学者感兴趣.
Chuck Lever不断提醒咱们关于Banga和Druschel关于Web服务器基准测试的论文.值得一读。
IBM有一篇名为Java服务器基准测试的优秀论文.[Baylor 等,2000年].值得一读。
Nginx 是一个web服务器,它使用目标操做系统上可用的任何高效网络事件机制.它变得很是流行;这甚至有关于它的两本书