2018 年春季更新:
我在 2010 年写了这篇回复,从那时起,Web 后端开发人员的世界发生了很多变化。即,“云”将一键式负载均衡器和自动缩放等服务转变为商品的出现,使得扩展应用程序的实际机制更容易上手。
也就是说,我在 2010 年在这篇文章中所写的内容在今天大部分情况下仍然适用,并且了解 Web 服务器和语言托管环境实际工作背后的机制以及如何对其进行调整可以为您节省大量托管成本。出于这个原因,我将这篇文章保留在下面最初写的内容,以供任何开始深入调整筹码的人使用。
1.取决于网络服务器(有时是这样的配置)。各种型号的说明:
带有 mpm_prefork 的 Apache(unix 上的默认值):按请求处理。为了最大限度地缩短启动时间,Apache 保留了一个空闲进程池,等待处理新请求(您可以配置其大小)。当一个新的请求进来时,主进程将它委托给一个可用的工作者,否则会产生一个新的。如果有 100 个请求进来,除非你有 100 个空闲的工作人员,否则需要进行一些分叉来处理负载。如果空闲进程的数量超过 MaxSpare 值,则在完成请求后会收获一些,直到只有这么多空闲进程。
具有 mpm_event、mpm_worker、mpm_winnt 的 Apache:每个请求的线程。同样,apache 在大多数情况下都会保留一个空闲线程池,也是可配置的。(一个小细节,但功能相同:mpm_worker 运行多个进程,每个进程都是多线程的)。
Nginx/Lighttpd:这些是轻量级的基于事件的服务器,它们使用 select()/epoll()/poll() 来多路复用多个套接字,而不需要多个线程或进程。通过非常仔细的编码和非阻塞 API 的使用,它们可以扩展到商品硬件上的数千个同时请求,提供可用带宽和正确配置的文件描述符限制。需要注意的是,在服务器上下文中实现传统的嵌入式脚本语言几乎是不可能的,这将抵消大部分好处。两者都支持 FastCGI,但是对于外部脚本语言。
2.取决于语言,或者在某些语言中,您使用哪种部署模型。某些服务器配置仅允许某些部署模型。
Apache mod_php、mod_perl、mod_python:这些模块为每个 apache worker 运行一个单独的解释器。其中大多数不能很好地与 mpm_worker 一起工作(由于客户端代码中线程安全的各种问题),因此它们大多仅限于分叉模型。这意味着对于每个 apache 进程,你都有一个 php/perl/python 解释器在里面运行。这严重增加了内存占用:如果一个给定的 apache worker 通常会在您的系统上占用大约 4MB 的内存,那么对于一个普通的应用程序来说,一个使用 PHP 的可能需要 15mb,而一个使用 Python 的可能需要 20-40MB。其中一些将是进程之间的共享内存,但总的来说,这些模型很难扩展到非常大的规模。
Apache(支持的配置)、Lighttpd、CGI:这主要是一种濒临灭绝的托管方法。CGI 的问题在于,您不仅要创建一个新进程来处理请求,而且还要对每个请求都这样做,而不仅仅是在需要增加负载时这样做。由于当今的动态语言具有相当长的启动时间,这不仅为您的网络服务器创建了大量工作,而且显着增加了页面加载时间。一个小的 perl 脚本可以作为 CGI 运行,但是一个大型的 python、ruby 或 java 应用程序相当笨重。在 Java 的情况下,您可能只需要等待一秒钟或更长时间才能启动应用程序,但必须在下一个请求时再次执行所有操作。
所有 Web 服务器,FastCGI/SCGI/AJP:这是运行动态语言的“外部”托管模型。有很多有趣的变化,但要点是您的应用程序侦听某种套接字,Web 服务器处理 HTTP 请求,然后通过另一个协议将其发送到套接字,仅适用于动态页面(静态页面是通常由网络服务器直接处理)。
这带来了许多优势,因为您需要的动态工作人员少于处理连接的能力。如果每 100 个请求中,有一半用于静态文件,例如图像、CSS 等,而且如果大多数动态请求都很短,那么您可能需要 20 个动态工作人员同时处理 100 个客户端。也就是说,由于给定网络服务器保持活动连接的正常使用是 80% 空闲,您的动态解释器可以处理来自其他客户端的请求。这比 mod_php/python/perl 方法要好得多,在这种方法中,当您的用户加载 CSS 文件或根本不加载任何内容时,您的解释器会使用内存而不做任何工作。
Apache mod_wsgi:这特别适用于托管 python,但它具有 web 服务器托管应用程序(易于配置)和外部托管(进程多路复用)的一些优点。当您在守护程序模式下运行它时,mod_wsgi 仅在需要时将请求委托给您的守护程序工作人员,因此 4 个守护程序可能能够同时处理 100 个用户(取决于您的站点及其工作负载)
Phusion Passenger:Passenger 是一个 apache 托管系统,主要用于托管 ruby 应用程序,与 mod_wsgi 一样,它提供了外部托管和网络服务器托管托管的优势。
3.同样,我将根据适用的托管模型来拆分问题。
mod_php、mod_python、mod_perl:只有应用程序的 C 库通常会在 apache 工作人员之间共享。这是因为 apache 首先分叉,然后加载您的动态代码(由于微妙之处,大多数情况下无法使用共享页面)。口译员在此模型中不相互交流。通常不共享全局变量。在 mod_python 的情况下,您可以让全局变量留在进程内的请求之间,但不能跨进程。这可能会导致一些非常奇怪的行为(浏览器很少永远保持相同的连接,并且大多数浏览器会打开几个到给定网站的连接)所以要非常小心如何使用全局变量。使用 memcached 或数据库或文件之类的东西来存储会话存储和其他需要共享的缓存位。
FastCGI/SCGI/AJP/Proxied HTTP:因为您的应用程序本身就是一个服务器,这取决于服务器编写的语言(通常与您的代码使用相同的语言,但并非总是如此)和各种因素。例如,大多数 Java 部署使用每个请求的线程。Python 及其“flup”FastCGI 库可以在 prefork 或线程模式下运行,但由于 Python 及其 GIL 受到限制,您可能会从 prefork 获得最佳性能。
mod_wsgi/passenger:服务器模式下的 mod_wsgi 可以配置它如何处理事情,但我建议你给它一个固定数量的进程。您希望将您的 python 代码保存在内存中,并准备好运行。这是保持延迟可预测和低的最佳方法。
在上面提到的几乎所有模型中,进程/线程的生命周期都比单个请求长。大多数设置都遵循 apache 模型的一些变化:保留一些备用工人,在需要时产生更多,在有太多时收获,基于一些可配置的限制。大多数这些设置 - 不会 - 在请求后破坏进程,尽管有些可能会清除应用程序代码(例如 PHP fastcgi 的情况)。
4.如果您说“网络服务器只能处理 100 个请求”,这取决于您是指实际的网络服务器本身还是网络服务器的动态部分。实际限制和功能限制之间也存在差异。
以 Apache 为例,您将配置最大数量的工作程序(连接)。如果此连接数为 100 并且已达到,则 apache 将不再接受任何连接,直到有人断开连接。启用 keep-alive 后,这 100 个连接可能会长时间保持打开状态,比单个请求长得多,而其他 900 个等待请求的人可能会超时。
如果您确实有足够高的限制,则可以接受所有这些用户。然而,即使使用最轻量级的 apache,每个工作人员的成本也约为 2-3mb,因此仅使用 apache,您可能会谈论 3gb+ 的内存来处理连接,更不用说其他可能有限的操作系统资源,如进程 ID、文件描述符、和缓冲区,这是在考虑您的应用程序代码之前。
对于 lighttpd/Nginx,它们可以在很小的内存占用中处理大量连接(数千个),通常每千个连接只有几兆(取决于缓冲区等因素以及异步 IO api 的设置方式)。如果我们继续假设您的大多数连接都保持活动状态并且 80%(或更多)空闲,那么这非常好,因为您不会浪费动态处理时间或大量内存。
在任何外部托管模型(mod_wsgi/fastcgi/ajp/proxied http)中,假设您只有 10 个工作人员和 1000 个用户发出请求,您的网络服务器会将请求排队到您的动态工作人员。这是理想的:如果您的请求快速返回,您可以继续处理更大的用户负载,而无需更多的工作人员。通常溢价是内存或数据库连接,通过排队,您可以使用相同的资源为更多用户提供服务,而不是拒绝某些用户。
请注意:假设您有一个页面构建报告或进行搜索并需要几秒钟,并且很多用户都将工作人员与此联系起来:想要加载您的首页的人可能会排队几秒钟,而所有那些长时间运行的请求完成。替代方案是使用单独的工作人员池来处理报告应用程序部分的 URL,或者单独进行报告(如在后台作业中),然后稍后轮询其完成情况。那里有很多选择,但需要您在应用程序中考虑一下。
5.大多数使用 apache 的人需要同时处理大量用户,由于内存占用高,请关闭 keep-alive。或者启用了保持活动的 Apache,保持活动时间限制很短,比如 10 秒(这样你就可以在单个页面加载中获取首页和图像/CSS)。如果您确实需要扩展到 1000 个或更多连接并且想要保持活动状态,您将需要查看 Nginx/lighttpd 和其他基于事件的轻量级服务器。
可能需要注意的是,如果您确实想要 apache(为了便于配置使用,或者需要托管某些设置),您可以使用 HTTP 代理将 Nginx 放在 apache 前面。这将允许 Nginx 处理 keep-alive 连接(最好是静态文件),而 apache 只处理 grunt 工作。有趣的是,Nginx 在编写日志文件方面也恰好比 apache 好。对于生产部署,我们对 apache 前面的 nginx 非常满意(在本例中使用 mod_wsgi)。apache 不做任何访问日志记录,也不处理静态文件,允许我们禁用 apache 内部的大量模块以保持其占用空间小。
我已经基本上回答了这个问题,但是不,如果您的连接时间很长,它不必对解释器运行多长时间有任何影响(只要您使用的是外部托管应用程序,现在应该清楚的是优越得多)。因此,如果您想使用彗星,并且要长时间保持活动状态(如果可以处理的话,这通常是一件好事)考虑使用 nginx。
额外的 FastCGI 问题 您提到 fastcgi 可以在单个连接中多路复用。协议确实支持这一点(我相信这个概念被称为“通道”),因此理论上单个套接字可以处理大量连接。但是,它不是 fastcgi 实现者的必需功能,实际上我不相信有一个服务器使用它。大多数 fastcgi 响应者也不使用此功能,因为实现此功能非常困难。大多数网络服务器一次只会通过给定的 fastcgi 套接字发出一个请求,然后通过该套接字发出下一个请求。因此,每个进程/线程通常只有一个 fastcgi 套接字。
您的 fastcgi 应用程序是使用处理还是线程(以及您是通过接受连接和委派的“主”进程实现它,还是通过大量进程来实现它,每个进程都在做自己的事情)取决于您;并且也会根据您的编程语言和操作系统的功能而有所不同。在大多数情况下,库使用的默认值应该没问题,但要准备好进行一些基准测试和参数调整。
至于共享状态,我建议您假装不存在任何传统的进程内共享状态使用:即使它们现在可以工作,您以后可能不得不将您的动态工作人员拆分到多台机器上。对于购物车等状态;db 可能是最好的选择,会话登录信息可以保存在 securecookies 中,对于临时状态,类似于 memcached 的东西非常简洁。您对共享数据的功能(“无共享”方法)的依赖越少,您在未来的规模就越大。
后记:我在上面的整个设置范围内编写并部署了很多动态应用程序:上面列出的所有网络服务器,以及 PHP/Python/Ruby/Java 范围内的所有内容。我已经对这些方法进行了广泛的测试(使用基准测试和实际观察),结果有时令人惊讶:少即是多。一旦您不再在网络服务器进程中托管您的代码,您通常可以使用极少数的 FastCGI/Mongrel/mod_wsgi/etc 工作人员。这取决于您的应用程序在数据库中停留的时间,但通常情况下,多于 2 * 数量的 CPU 的进程实际上不会为您带来任何好处。