C10K问题:是时候让Web服务器同时处理一万个客户端了

是时候让Web服务器同时处理一万个客户端了,您不觉得吗?毕竟,网络现在是一个很大的地方。

而且计算机也很大。您可以以1200美元左右的价格购买带有2 GB RAM的1000MHz机器和1000Mbit / sec以太网卡。我们来看-在20000个客户端上,每个客户端分别为50KHz,100Kbytes和50Kbits / sec。从磁盘上获取4 KB的数据并将其每秒发送给网络(每2万个客户端一次),所需要的功能不应该超过它。(顺便说一下,每个客户端的费用为0.08美元。某些操作系统收取的每客户端100美元的许可费用看起来有点沉重!)因此,硬件不再是瓶颈。

1999年,最繁忙的ftp网站之一cdrom.com实际上通过千兆以太网管道同时处理了10000个客户端。到2001年为止,几家ISP现在都提供相同的速度 ,他们希望这种速度在大型企业客户中越来越受欢迎。

瘦客户端计算模式似乎正在风起云涌—这次服务器已在Internet上,为成千上万的客户端提供服务。

考虑到这一点,这里有一些有关如何配置操作系统和编写代码以支持数千个客户端的说明。讨论围绕类似Unix的操作系统展开,因为这是我个人感兴趣的领域,但是Windows也涵盖了一点。

 

内容


内容

相关网站

本书先阅读

I / O框架

I / O策略

1.每个线程服务许多客户端,并使用非阻塞I / O和级别触发的就绪通知

2.为每个线程服务许多客户端,并使用非阻塞I / O和就绪状态更改通知

3.为每个服务器线程服务许多客户端,并使用异步I / O

4.为每个服务器线程服务一个客户端

5.将服务器代码构建到内核中

将TCP堆栈放入用户空间

注释

限制打开的文件句柄

线程限制

Java问题

其他技巧

其他限制

内核问题

评估服务器性能

例子

有趣的基于select()的服务器

有趣的基于/ dev / poll的服务器

有趣的基于epoll的服务器

有趣的基于kqueue()的服务器

有趣的基于实时信号的服务器

有趣的基于线程的服务器

有趣的内核服务器

其他有趣的链接


相关网站

请参阅Nick Black出色的Fast UNIX Servers 页面,以大约在2009年的情况下查看情况。

2003年10月,Felix von Leitner整理了一个出色的网页 和有关网络可伸缩性的演示文稿,并提供了比较各种网络系统调用和操作系统的基准测试。他的观察之一是2.6 Linux内核确实击败了2.4内核,但是有很多很多很好的图表可以使OS开发人员在一段时间内考虑一下。(另请参阅Slashdot 评论;很有趣的是,看看是否有人对Felix的结果进行了跟踪基准的改进。)

本书先阅读

如果您还没有阅读过,请出去阅读 W. Richard Stevens的 著作《 Unix网络编程:网络Apis:套接字和Xti(第1卷)》。它描述了与编写高性能服务器有关的许多I / O策略和陷阱。它甚至谈到“雷群”问题。同时,请阅读Jeff Darcy关于高性能服务器设计的说明

(另一本书对那些正在使用*而不是*写* Web服务器的人可能更有用的是 Cal Henderson的《构建可扩展网站》。)

I / O框架

提供了预打包的库,这些库可以抽象出下面介绍的一些技术,从而使您的代码与操作系统隔离,并使其更易于移植。

  • ACE是重量级的C ++ I / O框架,其中包含某些I / O策略和许多其他有用对象的面向对象的实现。特别是,他的Reactor是执行非阻塞I / O的OO方法,而Proactor是执行异步I / O的OO方法。
  • ASIO是一个C ++ I / O框架,正在成为Boost库的一部分。就像针对STL时代更新的ACE。
  • libevent是Niels Provos的轻量级C I / O框架。它支持kqueue和select,不久将支持poll和epoll。我认为,它只是水平触发的(什么是epoll的水平触发与边缘触发?两段代码彻底理解,它有好有坏。Niels有 一个很好的时间图表来处理一个事件 ,它是连接数的函数。它显示kqueue和sys_epoll是明确的赢家。
  • 我自己对轻量级框架的尝试(不幸的是,没有及时更新):
    • Poller是一个轻量级的C ++ I / O框架,它使用所需的任何基础就绪API(轮询,选择,/ dev / poll,kqueue或sigio)来实现级别触发的就绪API。对于比较各种API性能的基准测试很有用 本文档链接到下面的Poller子类,以说明如何使用每个就绪API。
    • rn是一个轻量级的CI / O框架,是我继Poller之后的第二次尝试。它是lgpl(因此更易于在商业应用程序中使用)和C(因此更易于在非C ++应用程序中使用)。它被用于一些商业产品。
  • Matt Welsh 在2000年4月撰写了一篇论文,内容涉及在构建可伸缩服务器时如何平衡工作线程和事件驱动技术的使用。本文介绍了他的Sandstorm I / O框架的一部分。
  • 科里·尼尔森的天秤!库 -Windows的异步套接字,文件和管道I / O库

I / O策略

网络软件的设计人员有很多选择。这里有一些:

  • 是否以及如何从单个线程发出多个I / O调用
    • 别; 始终使用阻塞/同步调用,并且可能使用多个线程或进程来实现并发
    • 使用非阻塞调用(例如,将套接字设置为O_NONBLOCK上的write())启动I / O,并使用就绪通知(例如poll()或/ dev / poll)来了解何时可以在该通道上启动下一个I / O 。通常仅可用于网络I / O,而不能用于磁盘I / O。
    • 使用异步调用(例如aio_write())启动I / O,并使用完成通知(例如信号或完成端口)来了解I / O何时完成。适用于网络和磁盘I / O。
  • 如何控制为每个客户端提供服务的代码
    • 每个客户端一个进程(1980年左右开始使用的经典Unix方法)
    • 一个OS级线程可处理许多客户端;每个客户由以下人员控制:
      • 用户级线程(例如,GNU状态线程,带有绿色线程的经典Java)
      • 状态机(有点深奥,但在某些圈子中很流行;我最喜欢)
      • 延续(有点深奥,但在某些圈子中很流行)
    • 每个客户端有一个操作系统级别的线程(例如具有本地线程的经典Java)
    • 每个活动客户端有一个操作系统级线程(例如,具有apache前端的Tomcat; NT完成端口;线程池)
  • 是使用标准O / S服务,还是将一些代码放入内核(例如,在自定义驱动程序,内核模块或VxD中)

以下五个组合似乎很受欢迎:

  1. 为每个线程服务许多客户端,并使用非阻塞I / O和级别触发的就绪通知
  2. 为每个线程服务许多客户端,并使用非阻塞I / O和就绪状态更改通知
  3. 为每个服务器线程服务许多客户端,并使用异步I / O
  4. 为每个服务器线程服务一个客户端,并使用阻塞I / O
  5. 将服务器代码构建到内核中

 

1.每个线程服务许多客户端,并使用非阻塞I / O和级别触发的就绪通知

...在所有网络句柄上设置非阻塞模式,并使用select()或poll()告诉哪个网络句柄有数据等待。这是传统的最爱。通过这种方案,内核会告诉您文件描述符是否已准备好,自从上次内核告诉您文件描述符以来,您是否已对该文件描述符执行了任何操作。(“级别触发”的名称来自计算机硬件设计;与“边缘触发”含义相反。乔纳森·莱蒙(Jonathon Lemon)在他的BSDCON 2000论文的kqueue()中介绍了这些术语 。)

注意:记住内核的就绪通知只是一个提示,这一点特别重要。当您尝试从文件描述符中读取文件描述符时,可能不再准备就绪。这就是为什么在使用就绪通知时使用非阻塞模式很重要的原因。

此方法的一个重要瓶颈是,如果当前页面不在内核中,则从磁盘块读取(read)或sendfile()。在磁盘文件句柄上设置非阻止模式无效。内存映射的磁盘文件也是如此。服务器第一次需要磁盘I / O时,其进程将阻塞,所有客户端都必须等待,并且原始的非线程性能浪费了。
这就是异步I / O的目的,但是在缺少AIO的系统上,执行磁盘I / O的工作线程或进程也可以解决此瓶颈。一种方法是使用内存映射文件,如果mincore()指示需要I / O,请要求工作人员执行I / O,然后继续处理网络流量。Jef Poskanzer提到Pai,Druschel和Zwaenepoel的1999 Flash Web服务器使用了该技巧。他们在 Usenix '99就可以了。看起来mincore()在BSD衍生的Unix(例如FreeBSD 和Solaris )中可用,但不属于Single Unix Specification由于Chuck Lever,它可以从Linux 2.3.51版开始作为Linux的一部分使用 。

但是 在2003年11月的freebsd-hackers名单上,Vivek Pei等人报告了 使用其Flash Web服务器的系统范围分析来解决瓶颈的非常好结果。他们发现一个瓶颈是mincore(猜想这毕竟不是一个好主意)。另一个瓶颈是sendfile在磁盘访问时阻塞。他们通过引入修改后的sendfile()来提高性能,该方法在获取磁盘页面尚不在核心时返回类似于EWOULDBLOCK的内容。(不确定您如何告诉用户该页面现在位于页面上……在我看来,这里真正需要的是aio_sendfile()。)优化的最终结果是,在1GHZ / 1GB FreeBSD盒子上,SpecWeb99得分约为800,这比spec.org上记录的任何内容都要好。

单线程可以通过多种方式来判断一组非阻塞套接字中的哪些已准备好进行I / O:

  • 传统的select()
    不幸的是,select()仅限于FD_SETSIZE句柄。该限制被编译到标准库和用户程序中。(某些C库版本使您可以在用户应用编译时提高此限制。)

    有关如何与其他就绪通知方案互换使用select()的示例,请参见 Poller_select (cc, h)。

  • 传统的poll()
    没有对poll()可以处理的文件描述符数量进行硬编码的限制,但是它确实变慢了大约数千,因为大多数文件描述符在任何时候都处于空闲状态,并且扫描了数千个文件描述符需要时间。

    某些操作系统(例如Solaris 8)通过使用轮询提示等技术来加速poll()等,该技术 由Niels Provos在1999年针对Linux 实施并进行了基准测试

    有关如何将poll()与其他就绪通知方案互换使用的示例,请参见 Poller_poll (cc, h, Benchmarks)。

  • / dev / poll - Solaris
    这是建议的Solaris轮询替代。

    / dev / poll背后的想法是利用经常以相同的参数多次调用poll()的事实。使用/ dev / poll,您可以打开/ dev / poll的句柄,并通过写入该句柄仅一次告诉OS您感兴趣的文件。从那时起,您只需从该句柄中读取一组当前准备好的文件描述符即可。

    它在Solaris 7中安静地出现(请参见patchid 106541),但它的首次公开出现在 Solaris 8中; 根据Sun的说法,在750个客户端上,这占poll()开销的10%。

    在Linux上尝试了/ dev / poll的各种实现,但是没有一个能像epoll一样出色,并且从未真正完成过。不建议在Linux上使用/ dev / poll。

    有关 如何与许多其他就绪通知方案互换使用/ dev / poll的示例,请参见 Poller_devpoll (cc, 基准)。(警告-该示例适用于Linux / dev / poll,在Solaris上可能无法正常工作。)

  • kqueue()- FreeBSD
    这是FreeBSD(以及不久之后的NetBSD)的建议轮询替代。

    见下文。 kqueue()可以指定边沿触发或电平触发。

  • 我大epoll()呢???

 

2.为每个线程服务许多客户端,并使用非阻塞I / O和就绪状态更改通知

就绪更改通知(或边缘触发的就绪通知)意味着您为内核提供了一个文件描述符,然后,当该描述符从未就绪过渡 到就绪时,内核会以某种方式通知您。然后,它假定您知道文件描述符已准备就绪,并且在您执行导致文件描述符不再准备就绪的操作之前(例如,直到收到关于以下内容的EWOULDBLOCK错误为止),该文件描述符将不再发送该类型的任何就绪通知。发送,接收或接受呼叫,或者发送或接收的传输少于请求的字节数)。

使用就绪状态更改通知时,您必须为虚假事件做好准备,因为一种常见的实现方式是,无论何时接收到任何数据包,无论文件描述符是否已准备就绪,都会发出就绪状态信号。

这与“ 级别触发 ”的就绪通知相反。编程错误的容忍度要低一些,因为如果您仅错过一个事件,那么该事件所针对的连接将永远卡住。尽管如此,我发现边缘触发的就绪通知使使用OpenSSL编程非阻塞客户端变得更加容易,因此值得尝试。

[Banga,Mogul,Drusha '99] 在1999年描述了这种方案。

有几种API可让应用程序检索“文件描述符准备就绪”通知:

 

3.为每个服务器线程服务许多客户端,并使用异步I / O

这在Unix中尚未流行,可能是因为很少有操作系统支持异步I / O,也可能是因为它(例如非阻塞I / O)需要重新考虑应用程序。在标准Unix下,aio_接口 (从该链接向下滚动到“异步输入和输出”)提供异步I / O,该接口将信号和值与每个I / O操作相关联。信号及其值被排队,并有效地传递给用户进程。这来自POSIX 1003.1b实时扩展,并且在Single Unix Specification版本2中。

AIO通常与边沿触发的完成通知一起使用,即操作完成时将信号排队。(它也可以通过调用aio_suspend()与级别触发的完成通知一起使用 ,尽管我怀疑很少有人这样做。)

glibc 2.1和更高版本提供了为实现标准合规性而不是性能而编写的通用实现。

从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 Mark Russinovich的Inside I / O Completion Ports,Jeffrey Richter的书“为Microsoft Windows 2000编程服务器端应用程序”(亚马逊, MSPress), 美国专利#06223207或 MSDN

 

4.为每个服务器线程服务一个客户端

...并让read()和write()块。具有为每个客户端使用整个堆栈框架的缺点,这会浪费内存。许多操作系统也难以处理数百个线程。如果每个线程获得2MB堆栈(这不是一个不常见的默认值),则在32位计算机上使用1GB用户可访问VM的(2 ^ 30/2 ^ 21)= 512个线程会耗尽*虚拟内存*(例如,例如,x86上通常提供的Linux)。您可以通过为每个线程分配较小的堆栈来解决此问题,但是由于大多数线程库不允许在创建后增加线程堆栈,因此这样做意味着设计程序以最大程度地减少堆栈使用。您也可以通过移至64位处理器来解决此问题。

Linux,FreeBSD和Solaris中的线程支持正在改善,即使对于主流用户,64位处理器也已迫在眉睫。也许在不久的将来,那些愿意为每个客户端使用一个线程的人甚至可以为10000个客户端使用该范例。不过,目前,如果您确实想支持那么多客户,则最好使用其他一些范例。

有关线程的观点,请参阅 HotOS IX上由Condit的von Behren和UCB的Brewer撰写的“ 为什么事件是个坏主意(对于高并发服务器)”。来自反线程阵营的任何人都要指出一篇反驳该论文的论文吗?:-)

Linux线程

LinuxTheads是标准Linux线程库的名称。自glibc2.0起,它已集成到glibc中,并且大多数与Posix兼容,但性能和信号支持不佳。

NGPT:适用于Linux的下一代Posix线程

NGPT是IBM 发起的一个项目,旨在为Linux提供良好的Posix兼容线程支持。它现在处于2.2的稳定版本,并且运行良好...但是NGPT团队 宣布 他们将NGPT代码库置于仅支持模式,因为他们认为这是“长期支持社区的最佳方法”。NGPT团队将继续致力于改善Linux线程支持,但现在专注于改善NPTL。(对NGPT小组的出色工作和向NPTL承认的优美方式表示敬意。)

NPTL:Linux的本地Posix线程库

NPTLUlrich Drepper (glibc的仁慈dict ^ H ^ H ^ H ^ Hmaintainer )和 Ingo Molnar的一个项目, 旨在为Linux提供世界一流的Posix线程支持。

从2003年10月5日起,NPTL现在已作为附加目录(就像linuxthreads)合并到glibc cvs树中,因此几乎可以肯定它将与glibc的下一个版本一起发布。

包含NPTL早期快照的第一个主要发行版是Red Hat9。(这对于某些用户来说有点不方便,但是有人必须打破僵局...)

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和内核更改,这些被称为本机Posix线程库(NPTL)。NPTL使用为NGPT设计的所有内核增强功能,并利用了一些新功能。Ingo Molnar 描述 了以下内核增强功能:

NPTL使用NGPT引入的三个内核功能:getpid()返回PID,CLONE_THREAD和futexes;NPTL还使用(并依赖)作为该项目一部分开发的更广泛的新内核功能集。

NGPT在2.5.8左右引入内核的某些项目已被修改,清理和扩展,例如线程组处理(CLONE_THREAD)。[影响NGPT兼容性的CLONE_THREAD更改已与NGPT人员同步,以确保NGPT不会以任何不可接受的方式破坏。]

设计白皮书http://people.redhat.com/drepper/nptl-design.pdf中描述了为NPTL开发并使用的内核功能。

简短列表:TLS支持,各种克隆扩展(CLONE_SETTLS,CLONE_SETTID,CLONE_CLEARTID),POSIX线程信号处理,sys_exit()扩展(在VM发行时发行TID futex),sys_exit_group()系统调用,sys_execve()增强并支持分离线程。

还有一些工作要扩展PID空间-例如。由于64K PID假设,max_pid和pid分配可伸缩性工作,导致procfs崩溃。另外,还完成了一些仅性能方面的改进。

本质上,新功能是对1:1线程的一种不妥协的方法-内核现在在可以改善线程的所有方面提供帮助,并且我们为每个基本线程原语精确地进行了最少必要的上下文切换和内核调用。

两者之间的一大区别是NPTL是1:1线程模型,而NGPT是M:N线程模型(请参见下文)。尽管如此, Ulrich的初始基准 似乎表明NPTL确实比NGPT快得多。(NGPT团队期待看到Ulrich的基准代码来验证结果。)

FreeBSD线程支持

FreeBSD支持LinuxThreads和用户空间线程库。另外,在FreeBSD 5.0中引入了称为KSE的M:N实现。有关一个概述,请访问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并行工作,不会以任何方式破坏它。实际上,通过测试共享位可以帮助使M:N线程更紧密。...

在2006年7月, Robert Watson提出1:1线程实现成为FreeBsd 7.x中的默认设置

我知道以前已经讨论过这个问题,但是我认为7.x会向前发展,是时候重新考虑了。在许多常见应用程序和场景的基准测试中,libthr展示了比libpthread更好的性能... libthr也已在我们许多平台上实现,并且已经在多个平台上实现了libpthread。我们对MySQL和其他重线程用户的第一个建议是“ Switch to libthr”,这也是有启发性的!...因此,草人建议是:将libthr设置为7.x上的默认线程库。

NetBSD线程支持

根据苏打则之记:

基于调度程序激活模型的内核支持的M:N线程库于2003年1月18日合并到NetBSD-current中。

有关详细信息,请参阅 在FREENIX '02上由Wasabi Systems,Inc.的Nathan J. Williams撰写的NetBSD操作系统上调度程序激活的实现

Solaris线程支持

Solaris中的线程支持正在发展……从Solaris 2到Solaris 8,默认线程库使用M:N模型,但是Solaris 9默认为1:1模型线程支持。请参见Sun的多线程编程指南 和有关Java和Solaris线程的Sun注释

JDK 1.3.x和更早版本中的Java线程支持

众所周知,直到JDK1.3.x的Java不支持任何处理网络连接的方法,每个客户端只有一个线程。 Volanomark是一个很好的微基准,它可以测量各种同时连接数下每秒的吞吐量。到2003年5月为止,来自各个供应商的JDK 1.3实现实际上能够处理上万个同时连接-尽管性能显着下降。有关 哪些JVM可以处理10000个连接以及随着连接数量增加性能如何下降的想法,请参阅表4

注意:1:1线程与M:N线程

实现线程库时有一个选择:您可以将所有线程支持放入内核(这称为1:1线程模型),也可以将其相当一部分移入用户空间(这称为M :N线程模型)。一方面,M:N被认为具有更高的性能,但它是如此复杂,以至于很难做到正确,并且大多数人正在远离它。

 

5.将服务器代码构建到内核中

据说Novell和Microsoft都在不同的时间执行了此操作,至少一个NFS实现执行了此操作, khttpd针对Linux和静态网页执行了此操作,而 “ TUX”(线程化linUX Web服务器) 是一个令人眼花fast乱的快速灵活的内核空间。 Ingo Molnar的HTTP服务器,用于Linux。Ingo在2000年9月1日的公告中 说,可以从ftp://ftp.redhat.com/pub/redhat/tux下载TUX的Alpha版本 ,并说明了如何加入邮件列表以获取更多信息。
linux-kernel列表一直在讨论这种方法的优缺点,并且共识似乎不是将Web服务器移入内核,而是应在内核中添加尽可能小的钩子以提高Web服务器性能。这样,其他类型的服务器都可以受益。参见例如 Zach Brown 关于userland vs.kernel http服务器的评论。似乎2.4 linux内核为用户程序提供了足够的功能,因为X15服务器的运行速度与Tux差不多,但是没有使用任何内核修改。

将TCP堆栈放入用户空间

例如,参见 netmap数据包I / O框架,以及基于该框架的 Sandstorm 概念验证Web服务器。

注释

理查德·古奇(Richard Gooch)撰写 了一篇讨论I / O选项的论文

在2001年,Tim Brecht和MMichal Ostrowski 测量了 基于简单选择服务器的各种策略。他们的数据值得一看。

在2003年,Tim Brecht发布 了userver的源代码,这是一个小型Web服务器,由Abhishek Chandra,David Mosberger,David Pariag和Michal Ostrowski编写的多台服务器组成。它可以使用select(),poll(),epoll()或sigio。

早在1999年3月, Dean Gaudet发表了

我不断被问到“你们为什么不使用像宙斯这样的基于选择/事件的模型?这显然是最快的。” ...

他的原因归结为“这真的很难,而且回报还不清楚”。然而,在短短几个月内,人们显然愿意为此付出努力。

标记Russinovich写了 一篇社论和 文章 讨论在2.2 Linux内核I / O战略的问题。值得一读,即使他似乎在某些方面也有误会。特别是,他似乎认为Linux 2.2的异步I / O(请参见上面的F_SETSIG)不会在数据准备就绪时通知用户进程,而只是在新连接到达时通知用户进程。这似乎是一种奇怪的误解。另请参见 上较早草案的意见, 30英格·蒙内的反驳1999年4月, 2 Russinovich的意见1999年5月, 一个反驳的阿伦·考克斯,以及各种 岗位到Linux内核。我怀疑他是想说Linux不支持异步磁盘I / O,这曾经是真的,但是现在SGI已经实现了KAIO,现在不再是真的了。

有关“完成端口”的信息,请参见sysinternals.com和 MSDN上的这些页面。简而言之,win32的“重叠I / O”级别太低,以至于不方便使用;“完成端口”是提供完成事件队列的包装器,此外还提供调度魔术,该魔术试图保持运行次数如果已经从该端口拾取完成事件的其他线程正在休眠(也许正在阻塞I / O),则允许更多线程拾取完成事件来使线程保持恒定。

另请参阅OS / 400对I / O完成端口的支持

还有在1999年9月在题为Linux内核一个有趣的讨论“ > 15,000u同时连接 ”(和第二周的线程)。强调:

  • 埃德·霍尔(Ed Hall) 就他的经历发表了一些笔记;他在运行Solaris的UP P2 / 333上实现了每秒1000次以上的连接。他的代码使用了一个小的线程池(每个CPU 1或2个线程),每个线程都使用“基于事件的模型”管理大量的客户端。
  • Mike Jagdis 发表了对轮询/选择开销的分析,并说:“当前的选择/轮询实现可以显着改善,尤其是在阻塞情况下,但是开销会随着描述符数量的增加而增加,因为选择/轮询不会,并且不能,记住哪些描述符很有趣。使用新的API可以轻松解决。欢迎提出建议...”
  • 迈克(Mike)发表了他在改进select()和poll()方面的工作
  • Mike 发表了一些关于可能使用的API来代替poll()/ select()的内容:“在编写“ pollfd like”结构的“ device like” API怎么样,“ device”侦听事件并传递“ pollfd like”结构在阅读时代表他们吗?...“
  • Rogier Wolff 建议 使用“数字专家建议的API”, http://www.cs.rice.edu/~gaurav/papers/usenix99.ps
  • Joerg Pommnitz 指出,遵循这些思路的任何新API不仅应等待文件描述符事件,而且还应等待信号以及SYSV-IPC。我们的同步原语当然应该至少能够完成Win32的WaitForMultipleObjects所能做的事情。
  • Stephen Tweedie 断言,F_SETSIG,排队的实时信号和sigwaitinfo()的组合是http://www.cs.rice.edu/~gaurav/papers/usenix99.ps中提出的API的超集。他还提到,如果您对性能感兴趣,可以始终保持信号阻塞。而不是异步传递信号,该过程使用sigwaitinfo()从队列中获取下一个信号。
  • Jayson Nordwick  完成端口与F_SETSIG同步事件模型进行了比较,得出的结论非常相似。
  • 艾伦·考克斯 Alan Cox)指出,2.3.18ac中包含了SCT SIGIO补丁的较旧版本。
  • Jordan Mendelson 发布了一些示例代码,展示了如何使用F_SETSIG。
  • Stephen C. Tweedie 继续对完成端口和F_SETSIG进行比较,并指出:“通过信号出队机制,如果库使用相同的机制,则您的应用程序将获得发往各个库组件的信号,”但该库可以设置它自己的信号处理程序,因此这不会影响程序。
  • Doug Royer 指出,他在Sun日历服务器上工作时,在Solaris 2.6上已获得100,000个连接。其他人则对Linux上需要多少RAM以及将遇到哪些瓶颈进行了估算。

有趣的阅​​读!

 

限制打开的文件句柄

  • 任何Unix:由ulimit或setrlimit设置的限制。
  • Solaris:请参阅Solaris FAQ,问题3.46(或其有关;它们会定期对问题重新编号)。
  • FreeBSD:

    编辑/boot/loader.conf,添加以下行
    set kern.maxfiles=XXXX
    其中XXXX是所需的文件描述符系统限制,然后重新启动。感谢一位匿名读者,他写信说他已经在FreeBSD 4.3上实现了10000多个连接,并说
    “ FWIW:您实际上无法通过sysctl来微调FreeBSD中的最大连接数。您必须在/boot/loader.conf文件中
    进行此操作。 其原因是zalloci()调用用于初始化套接字和tcpcb结构的区域很早就在系统启动时发生,以使该区域既是类型稳定的又是可交换的。
    您还需要将mbufs的数量设置得更高,因为(在未修改的情况下)内核)为tcptempl结构的每个连接消耗一个mbuf,用于实现keepalive。”
    另一位读者说
    “从FreeBSD 4.4开始,不再分配tcptempl结构;您不必担心每个连接都会占用一个mbuf。”
    也可以看看:
  • OpenBSD:读者说
    “在OpenBSD中,需要进行额外的调整以增加每个进程可用的打开文件句柄的数量: /etc/login.conf中的openfiles-cur参数 需要增加。您可以使用sysctl -w或in更改kern.maxfiles。 sysctl.conf,但没有任何作用。这很重要,因为在出​​厂时,login.conf限制对于非特权进程来说是64的极低,对于特权特权是128。
  • Linux:请参阅Bodo Bauer的/ proc文档。在2.4内核上:
    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
    增加当前过程的限制。

    我验证了Red Hat 6.0(2.2.5左右,加上补丁)上的进程可以以此方式打开至少31000个文件描述符。另一位同仁已验证2.2.12上的进程可以这种方式(具有适当的限制)打开至少90000个文件描述符。上限似乎是可用内存。
    Stephen C. Tweedie 发布了 有关如何在启动时使用initscript和pam_limit全局或每用户设置ulimit限制的信息。
    但是,在较早的2.2内核中,即使进行了上述更改,每个进程的打开文件数仍限制为1024。
    另请参见 Oskar的1998年文章,该文章讨论了2.0.36内核中文件描述符在每个进程和整个系统范围内的限制。

 

线程限制

在任何体系结构上,您可能需要减少为每个线程分配的堆栈空间量,以避免虚拟内存用完。如果使用pthread,则可以在运行时使用pthread_attr_init()进行设置。

  • Solaris:我听说它支持的内存数量尽可能多。
  • 带有NPTL:/ proc / sys / vm / max_map_count的Linux 2.6内核可能需要增加到32000左右的线程以上。(不过,除非您使用的是64位处理器,否则您将需要使用非常小的堆栈线程来获得接近该数量的线程。)请参阅NPTL邮件列表,例如主题为“ 不能创建超过32K的线程” 线程? ”,以获取更多信息。
  • Linux 2.4:/ proc / sys / kernel / threads-max是最大线程数;在我的Red Hat 8系统上默认为2047。您可以像往常一样通过将新值回显到该文件中来设置增加此值,例如“ echo 4000> / proc / sys / kernel / threads-max”
  • Linux 2.2:至少在Intel上,甚至2.2.13内核也限制了线程数。我不知道其他架构的局限性。 Mingo在Intel上发布了2.1.131的补丁程序,删除了此限制。它似乎已集成到2.3.20中。

    另请参见Volano有关在2.2内核中提高文件,线程和FD_SET限制的详细说明。哇。本文档为您介绍了很多东西,这些东西很难弄清楚自己,但是有些过时了。

  • Java:请参阅Volano的详细基准测试信息,以及有关如何调整各种系统 以处理大量线程的信息。

Java问题

在JDK 1.3之前,Java的标准网络库主要提供了“ 每个客户端一个线程”模型。有一种方法可以进行非阻塞读取,但是没有方法可以进行非阻塞写入。

2001年5月,JDK 1.4引入了程序包java.nio, 以提供对非阻塞I / O(以及其他一些功能)的完全支持。有关某些警告,请参见发行说明。试试看,并给Sun反馈!

惠普的Java还包括一个线程轮询API

2000年,Matt Welsh为Java实现了非阻塞套接字。他的性能基准表明,与阻塞处理许多(最多10000个)连接的服务器中的套接字相比,它们具有优势。他的课程库称为 java-nbio;这是Sandstorm项目的一部分 。可以使用显示10000个连接的性能的基准 。

另请参见 Dean Gaudet 关于Java,网络I / O和线程的文章,以及 Matt Welsh关于事件与工作线程的论文。

在NIO之前,有一些改进Java网络API的建议:

  • Matt Welsh的 Jaguar系统 提出了预序列化的对象,新的Java字节码和内存管理更改,以允许在Java中使用异步I / O。
  • CC 将Java与虚拟接口体系结构接口。Chang和T. von Eicken提出了内存管理更改,以允许在Java中使用异步I / O。
  • JSR-51 是随java.nio软件包提供的Sun项目。Matt Welsh参加了(谁说Sun不听?)。

其他技巧

  • 零复制
    正常情况下,数据从这里到那里会被多次复制。将这些副本消除到最低限度的物理方案称为“零副本”。
    • Thomas Ogrisegg在Linux 2.4.17-2.4.20下针对mmaped文件的零拷贝发送补丁。声称它比sendfile()更快。
    • IO-Lite 是针对一组I / O原语的建议,它消除了对许多副本的需求。
    • 艾伦·考克斯(Alan Cox)指出,零拷贝有时不值得在1999年解决。(尽管他确实喜欢sendfile()。)
    • Ingo 于2000年7月在TUX 1.0的2.4内核中实现了一种零拷贝TCP形式,并表示他很快将其提供给用户空间。
    • Drew Gallatin和Robert Picco在FreeBSD中增加了一些零拷贝功能。这个想法似乎是,如果您在套接字上调用write()或read(),则指针是页面对齐的,并且传输的数据量至少是一页,并且*并且*您不会立即重用缓冲区,将使用内存管理技巧来避免复制。但是请在linux内核上查看 此消息的后续内容,以 使人们对这些内存管理技巧的速度感到担忧。

      根据苏打则之记:

      自NetBSD-1.6发行以来,通过指定内核选项“ SOSEND_LOAN”,支持发送方零复制。现在,此选项是NetBSD-current上的默认选项(您可以通过在NetBSD_current的内核选项中指定“ SOSEND_NO_LOAN”来禁用此功能)。使用此功能,如果将大于4096字节的数据指定为要发送的数据,则会自动启用零复制。
    • sendfile()系统调用可以实现零复制网络。
      Linux和FreeBSD中的sendfile()函数使您可以告诉内核发送部分或全部文件。这使OS可以尽可能高效地执行操作。它可以在使用线程的服务器或使用非阻塞I / O的服务器中同样出色地使用。(在Linux中,它是目前不良记录; 使用_syscall4叫它安迪Kleen的是写作,涵盖这一新手册页见也。探索的sendfile系统调用由杰夫·特兰特在Linux公报发布91) 有传言说,FTP .cdrom.com从sendfile()中明显受益。

      对于2.4内核,sendfile()的零复制实现正在进行中。参见LWN 2001年1月25日

      一位将sendfile()与Freebsd一起使用的开发人员报告说,使用POLLWRBAND而不是POLLOUT会有很大的不同。

      Solaris 8(从2001年7月开始更新)具有一个新的系统调用“ sendfilev”。 手册页的副本在这里。。Solaris 8 7/01 发行说明 也提到了它。我怀疑这在以阻塞模式发送到套接字时最有用。与无阻塞套接字一起使用会有些痛苦。

  • 通过使用writev(或TCP_CORK)避免小帧
    Linux下的一个新套接字选项TCP_CORK告诉内核避免发送部分帧,这会有所帮助,例如,当有很多小的write()调用无法捆绑在一起时某些原因。取消设置该选项将刷新缓冲区。不过,最好使用writev()...

    有关在Linux内核上有关TCP_CORK和可能的替代MSG_MORE的一些非常有趣的讨论的摘要,请参见LWN 2001年1月25日

  • 明智地处理过载。
    Provos,Lever和Tweedie 2000 ]指出,当服务器过载时,断开传入连接可以改善性能曲线的形状,并降低总体错误率。他们使用了平滑版本的“准备好I / O的客户端数量”来衡量过载。该技术应该很容易适用于使用select,poll或任何每次调用返回准备事件计数的系统调用编写的服务器(例如/ dev / poll或sigtimedwait4())。
  • 某些程序可以从使用非Posix线程中受益。
    并非所有线程都是平等的。例如,Linux(及其在其他操作系统中的朋友)中的clone()函数可让您创建一个具有其自己当前工作目录的线程,这对于实现ftp服务器非常有帮助。有关使用本机线程而不是pthread的示例,请参见Hoser FTPd。
  • 缓存自己的数据有时可能是一个胜利。
    Vivek Sadananda Pai(vivek@cs.rice.edu)在5月9日在new-httpd上发表的“ Re:解决混合服务器问题” 指出:

    “在FreeBSD和Solaris / x86上,我已将基于选择的服务器与多进程服务器的原始性能进行了比较。在微基准测试中,由于软件体系结构而导致的性能差异很小。基于服务器的服务器源于应用程序级缓存,虽然多进程服务器可以以更高的成本实现它,但要在实际工作负载上获得相同的收益(相对于微基准测试)则更加困难。那会出现在下次的Usenix会议论文。如果你有跋,该文件可在 http://www.cs.rice.edu/~vivek/flash99/ “

其他限制

  • 旧的系统库可能使用16位变量来保存文件句柄,这会在32767句柄以上引起麻烦。glibc2.1应该可以。
  • 许多系统使用16位变量来保存进程或线程ID。将Volano可伸缩性基准移植到C,看看各种操作系统的线程数上限是多少会很有趣。
  • 某些操作系统预分配了过多的线程本地内存。如果每个线程获得1MB,并且虚拟机总空间为2GB,则将创建2000个线程的上限。
  • 查看http://www.acme.com/software/thttpd/benchmarks.html底部的性能比较图 。请注意,即使在Solaris 2.6上,各种服务器在128个以上的连接上也有问题吗?知道原因的人,请告诉我。
    注意:如果TCP堆栈有一个导致在SYN或FIN时短暂(200ms)延迟的错误(如Linux 2.2.0-2.2.6那样),并且OS或http守护程序对打开的连接数有硬限制,您完全可以期待这种行为。可能还有其他原因。

内核问题

对于Linux,内核瓶颈似乎一直在被修复。请参阅《Linux周报》,《 内核流量》, 《 Linux-内核邮件列表》和我的《 Mindcraft Redux》页面

1999年3月,Microsoft发起了一个基准测试,将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%。

评估服务器性能

特别是以下两个测试是简单,有趣和困难的:

  1. 每秒原始连接数(每秒可以服务多少个512字节文件?)
  2. 具有许多慢速客户端的大文件的总传输率(在性能达到预期之前可以从服务器同时下载多少28.8k调制解调器客户端?)

Jef Poskanzer发布了比较许多Web服务器的基准测试。有关 其结果,请参见http://www.acme.com/software/thttpd/benchmarks.html

我也有 一些关于将thttpd与Apache进行比较的旧笔记,这可能是初学者感兴趣的。

Chuck Lever不断提醒我们有关 Banga和Druschel关于Web服务器基准测试的论文。值得一读。

IBM有一篇出色的论文,标题为《Java服务器基准测试》 [Baylor等,2000]。值得一读。

例子

Nginx是一个Web服务器,它使用目标OS上可用的任何高效网络事件机制。它越来越流行;甚至还有 关于它的书籍(由于此页面最初是撰写的,因此还有更多,包括该书的第四版。)

有趣的基于select()的服务器

有趣的基于/ dev / poll的服务器

  • N. Provos,C.杠杆, “可扩展的网络I / O在Linux中,” 月,2000。[FREENIX轨道,PROC。USENIX 2000,加利福尼亚州,圣地亚哥(2000年6月)。]描述了修改为支持/ dev / poll的thttpd版本。性能与phhttpd进行了比较。

有趣的基于epoll的服务器

  • ribs2
  • cmogstored-将epoll / kqueue用于大多数联网,将线程用于磁盘并接受4

有趣的基于kqueue()的服务器

有趣的基于实时信号的服务器

  • 铬的 X15。它使用2.4内核的SIGIO功能以及sendfile()和TCP_CORK,据报道甚至达到了比TUX更高的速度。该源可根据社区源(非开源)许可证获得。请参阅 Fabio Riccardi 的原始公告
  • Zach Brown的 phhttpd- “被编写来展示sigio / siginfo事件模型的快速Web服务器。如果您在生产环境中尝试使用此代码,则应将其视为高度实验性的代码,并高度自觉。” 使用2.3.21或更高版本的siginfo功能,并包括早期内核所需的补丁程序。据说比khttpd还要快。请参阅他在1999年5月31日的帖子中 的一些注释。

有趣的基于线程的服务器

有趣的内核服务器

其他有趣的链接

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页