关于网站开发的一些基本但重要的问题?

我已经开发了一些基于Web的应用程序,直到现在使用PHP,Python和Java。 但是一些基本的但是非常重要的问题仍然是我所不了解的,所以我做了这个post来得到你们的帮助和澄清。

说我使用一些编程语言作为我的后端语言(PHP / Python / .Net / Java等),我部署我的应用程序与Web服务器(Apache / Lighttpd / Nginx / IIS等)。 假设在时间T,我的一个页面有100个来自不同用户的同时请求。 所以我的问题是:

  1. 我的Web服务器如何处理这样的100个同时请求? Web服务器会为每个请求生成一个进程/线程吗? (如果是,进程或线程?)
  2. 后端语言的解释器如何工作? 它将如何处理请求并生成正确的html? 解释器是否会为每个请求生成一个进程/线程?(如果是,进程或线程?)
  3. 如果解释器会为每个请求生成一个进程/线程,那么这些进程(线程)怎么样? 他们会分享一些代码空间吗? 他们会互相沟通吗? 如何处理后端代码中的全局variables? 或者他们是独立的进程(线程)? 进程/线程的持续时间有多长? 当请求被处理并且返回响应时它们会被销毁吗?
  4. 假设networking服务器只能同时支持100个请求,但是现在却有1000个并发请求。 它如何处理这种情况? 它会像队列一样处理它们,并在服务器可用时处理请求吗? 还是其他的方法?
  5. 我最近读了一些关于彗星的文章。 而且我发现长时间连接可能是处理实时多用户用例的一个好方法。 那么长连接怎么样? 它是一些特定的Web服务器的function,或者它可用于每个Web服务器? 长连接将需要一个长期存在的解释过程?

谢谢大家。 这些问题让我非常恼火。 所以希望你能帮忙。 更详细的答案将大大赞赏。 并请附上一些参考资料。

问候。


编辑:最近我读了一些关于CGI和fastcgi的文章,这让我知道fastcgi的方法应该是一个典型的方法来处理请求。

该协议在多个独立的FastCGI请求之间复用单个传输连接。 这支持能够使用事件驱动或multithreading编程技术处理并发请求的应用程序。

引用fastcgi规范 ,其中提到可以处理多个请求的连接 ,并可以在multithreading技术中实现。 我想知道这个连接可以被视为进程 ,它可以为每个请求生成几个线程 。 如果这是真的,我更加困惑如何处理每个线程中的共享资源?

PS谢谢托马斯的build议,把这个post分成几个post,但是我认为这些问题是相关的,最好把它们组合在一起。

非常感谢S.Lott的回答,但每个问题的答案都很简短,或者根本没有涉及。

谢谢大家的回答,这让我更接近事实。

详细的答案将非常感激!

1.取决于networking服务器(有时configuration这样的)。 各种型号的描述:

  • Apache与mpm_prefork(在UNIX上默认):每个请求的进程。 为了最小化启动时间,Apache保留一个空闲进程池等待处理新的请求(你configuration的大小)。 当一个新的请求进来时,主进程把它委托给一个可用的工作者,否则产生一个新的请求。 如果有100个请求进来,除非你有100个闲置的工人,否则需要做一些分叉来处理负载。 如果空闲进程的数量超过MaxSpare值,那么在完成请求之后会收集一些进程,直到只有很多空闲进程。

  • Apache与mpm_event,mpm_worker,mpm_winnt:每个请求的线程。 同样,apache在大多数情况下都保留一个空闲线程池,也是可configuration的。 (一个小的细节,但function相同:mpm_worker运行多个进程,每个进程是multithreading的)。

  • Nginx / Lighttpd:这些是基于事件的轻量级服务器,使用select()/ epoll()/ poll()来复用多个套接字,而不需要多个线程或进程。 通过非常小心的编码和使用非阻塞API,它们可以在商品硬件上同时扩展到数千个请求,提供可用带宽和正确configuration的文件描述符限制。 需要注意的是,在服务器环境中实现传统的embedded式脚本语言几乎是不可能的,这将会消除大部分好处。 但是,两者都支持FastCGI用于外部脚本语言。

2.取决于您使用的部署模型的语言或某些语言。 某些服务器configuration只允许某些部署模型。

  • Apache mod_php,mod_perl,mod_python:这些模块为每个apache worker运行一个单独的解释器。 其中大多数不能很好地与mpm_worker一起工作(由于客户端代码中存在thread安全的各种问题),因此它们大多局限于分叉模型。 这意味着对于每个Apache进程,你都有一个php / perl / python解释器在里面运行。 这严重增加了内存占用量:如果一个给定的apache worker通常在系统上占用大约4MB内存,那么使用PHP的人可能需要15mb,而使用python的人可能需要20-40MB来处理平均应用程序。 其中一些将在进程之间共享内存,但总的来说,这些模型非常难以扩展。

  • Apache(受支持的configuration),Lighttpd,CGI:这主要是一种托pipe方法。 CGI的问题是,你不仅要分叉一个新的处理请求的过程,而且还要为-every-请求这样做,而不仅仅是当你需要增加负载时。 使用今天的dynamic语言启动时间相当长,这不仅为您的Web服务器创造了大量工作,而且大大增加了页面加载时间。 一个小的perl脚本可能会像CGI一样运行,但是一个大型的python,ruby或者java应用程序是相当笨拙的。 在Java的情况下,您可能正等待第二个或更多的应用程序启动,只能在下一个请求中再次完成。

  • 所有Web服务器,FastCGI / SCGI / AJP:这是运行dynamic语言的“外部”托pipe模型。 有一个有趣的变化的完整列表,但要点是你的应用程序监听某种套接字,并且Web服务器处理一个HTTP请求,然后通过另一个协议发送到套接字,只为dynamic页面(静态页面通常由networking服务器直接处理)。

    这具有许多优点,因为您需要的dynamic员工less于需要处理连接的能力。 如果每100个请求,一半是静态文件,如图像,CSS等,此外,如果大多数dynamic请求很短,你可能会得到与20个dynamic工作人员处理100个同时客户端。 也就是说,由于一个给定的networking服务器保持连接的正常使用是80%闲置,你的dynamic解释器可以处理来自其他客户端的请求。 这比mod_php / python / perl方法好得多,当你的用户加载一个CSS文件或者根本不加载任何东西的时候,你的解释器就会使用内存,而不是做任何工作。

  • Apache mod_wsgi:这特别适用于托pipepython,但它需要Web服务器托pipe的应用程序(简单configuration)和外部托pipe(进程多路复用)的一些优势。 当以守护进程模式运行时,mod_wsgi只会在需要时将请求委托给守护进程工作,因此4个守护进程可能能够处理100个并发用户(取决于您的站点及其工作负载)

  • Phusion Passenger:Passenger是一个主要用于托piperuby应用程序的apache托pipe系统,就像mod_wsgi提供了外部和web服务器托pipe的优点。

3.再次,我将根据托pipe模式将问题分为适用的地方。

  • mod_php,mod_python,mod_perl:只有你的应用程序的C库通常会在apache worker之间共享。 这是因为apache首先分叉,然后加载你的dynamic代码(由于微妙,大多不能使用共享页面)。 口译员不能在这个模型中相互沟通。 通常不共享全局variables。 在mod_python的情况下,您可以在进程内的请求之间保留全局variables,但不能跨进程。 这可能会导致一些非常奇怪的行为(浏览器很less保持相同的连接永远,并最多打开几个给定的网站),所以要非常小心你如何使用全局。 使用类似memcached或数据库或文件的东西,如会话存储和其他需要共享的caching位。

  • FastCGI / SCGI / AJP / Proxied HTTP:由于您的应用程序本质上是一个服务器,这取决于服务器所用的语言(通常与您的代码相同的语言,但不总是)以及各种因素。 例如,大多数Java部署使用一个请求线程。 Python和它的“flup”FastCGI库可以在prefork或者thread模式下运行,但是由于Python和GIL的限制,你可能会从prefork中获得最好的性能。

  • mod_wsgi / passenger:mod_wsgi在服务器模式下可以configuration它是如何处理的,但是我build议你给它一个固定数量的进程。 你想保持你的Python代码在内存中,启动并准备好去。 这是保持延迟可预测和最低的最佳方法。

在上面提到的几乎所有模型中,进程/线程的生命周期比单个请求更长。 大多数设置遵循apache模型的一些变化:保留一些闲置的工作人员,在需要时产生更多,根据一些可configuration的限制收获太多。 大多数这些设置不会在请求之后破坏进程,尽pipe有些可能会清除应用程序代码(例如在使用PHP fastcgi的情况下)。

4.如果您说“Web服务器只能处理100个请求”,则取决于您是指实际的Web服务器本身还是Web服务器的dynamic部分。 实际和function限制之间也有区别。

以Apache为例,您将configuration最大数量的工人(连接)。 如果这个连接数是100,并且达到了,那么在有人断开连接之前,不会有更多的连接被apache接受。 启用保持连接后,这100个连接可能会长时间保持打开状态,比单个请求长得多,另外900个等待请求的人可能会超时。

如果你有足够的限制,你可以接受所有的用户。 但是,即使使用最轻量级的apache,每个工作人员的成本大约是2-3mb,所以单独使用apache,您可能只是为了处理连接而讨论3gb +的内存,更不用说其他可能有限的操作系统资源,如进程id,文件描述符,和缓冲区,这是在考虑你的应用程序代码之前。

对于lighttpd / Nginx,他们可以在一个很小的内存空间中处理大量的连接(数千个),通常每千个连接只有几兆(取决于缓冲区以及如何设置asynchronousIO等因素)。 如果我们继续假设你的连接保持活跃状态​​,并且空闲80%(或更多),这非常好,因为你不会浪费dynamic处理时间或大量内存。

在任何外部托pipe模型(mod_wsgi / fastcgi / ajp / proxied http)中,假设您只有10个工作人员和1000个用户提出请求,则您的Web服务器会将请求排入dynamic工作人员。 这是理想的:如果您的请求快速返回,您可以继续处理更大的用户负载,而不需要更多的工作人员。 通常情况下,溢价是内存或数据库连接,通过排队可以为更多的用户提供相同的资源,而不是拒绝某些用户。

要小心:比如说你有一个页面可以build立一个报告或者做一个search并且需要几秒钟的时间,而且很多用户会把这个工作关联起来:有人想要加载你的头版,可能会排队几秒钟,而所有这些长时间运行的请求完成。 替代scheme是使用单独的工作人员池来处理报告应用程序部分的URL,或者分别进行报告(如在后台作业中),然后在后期对其完成情况进行投票。 那里有很多的select,但要求你把一些想法放到你的应用程序中。

5.大多数使用apache的人需要处理大量的并发用户,由于内存占用大的原因,closureskeep-alive。 或者启用保持活动状态的Apache,保持活动时间较短,例如10秒(因此,您可以在单页加载中获得首页和图像/ CSS)。 如果您真的需要扩展到1000个以上的连接并希望保持活跃,您将需要查看Nginx / lighttpd和其他基于轻量级事件的服务器。

可能会注意到,如果你确实需要apache(为了configuration的方便使用,或者需要托pipe某些设置),你可以把Nginx放在apache之前,使用HTTP代理。 这将允许Nginx处理保持活动连接(最好是静态文件)和apache只处理咕噜的工作。 有趣的是,Nginx在编写日志文件时也比apache更好。 对于生产部署,我们对Apache前面的nginx非常满意(在这个例子中是mod_wsgi)。 Apache不做任何访问日志logging,也不处理静态文件,允许我们禁用Apache中的大量模块,以保持它的小脚印。

我已经主要回答了这个问题,但不是,如果你有很长的连接,解释器运行多长时间不必有任何影响(只要你使用外部托pipe应用程序,现在应该清楚非常优越)。 所以,如果你想使用彗星,并保持长时间(这通常是一件好事,如果你能处理它)考虑nginx。

Bonus FastCGI问题您提到fastcgi可以在单个连接中复用。 这是协议确实支持的(我相信这个概念被称为“通道”),所以理论上一个套接字可以处理大量的连接。 然而,这并不是fastcgi实现者必需的function,实际上我不相信有一个服务器使用它。 大多数fastcgi响应者也不使用这个特性,因为实现这个非常困难。 大多数web服务器一次只能在一个给定的fastcgi套接字中发出一个请求,然后在该套接字上进行下一个请求。 所以你通常每个进程/线程只有一个fastcgi套接字。

无论您的fastcgi应用程序是使用处理还是线程(无论您是通过“主”进程来实现它,而是接受连接和委托,或者只是执行大量的进程都是自己的事情) 并根据您的编程语言和操作系统的function而变化。 在大多数情况下,无论默认情况下,图书馆使用的应该是好的,但准备做一些基准和调整参数。

至于共享状态,我build议你假装任何传统的进程间共享状态的使用都不存在:即使他们现在可以工作,你可能不得不在晚些时候将你的dynamic工作者分配到多个机器上。 对于像购物车等状态; 数据库可能是最好的select,会话login信息可以保存在安全的cookie,临时状态类似于memcached是相当整洁。 您依赖共享数据的function越less(“无共享”方法),您可以在未来扩大规模。

后记 :我已经在上面的整个范围内编写和部署了大量的dynamic应用程序:上面列出的所有Web服务器,以及PHP / Python / Ruby / Java范围内的所有内容。 我已经广泛地testing(使用基准testing和现实世界的观察)的方法,结果有时令人惊讶:通常更less。 一旦从Web服务器进程中托pipe代码,通常可以使用非常less量的FastCGI / Mongrel / mod_wsgi / etc工作程序。 这取决于您的应用程序在数据库中保留了多less时间,但是经常出现的情况是,比2 *个CPU数量更多的进程实际上不会为您带来任何收益。

我的Web服务器如何处理这样的100个同时请求? Web服务器是否会为每个请求生成一个进程/线程? (如果是,进程或线程?)

它有所不同。 Apache具有处理请求的线程和进程。 Apache启动几个并发进程,每个进程都可以运行任意数量的并发线程。 您必须configurationApache来控制每个请求实际执行的方式。

后端语言的解释器如何工作? 它将如何处理请求并生成正确的html? 解释器是否会为每个请求生成一个进程/线程?(如果是,进程或线程?)

这取决于你的Apacheconfiguration和你的语言。 对于Python来说,一个典型的方法是让守护进程在后台运行。 每个Apache进程都拥有一个守护进程。 这是用mod_wsgi模块完成的。 它可以被configuration为工作几种不同的方式。

如果解释器会为每个请求生成一个进程/线程,那么这些进程(线程)怎么样? 他们会分享一些代码空间吗? 他们会互相沟通吗? 如何处理后端代码中的全局variables? 或者他们是独立的进程(线程)? 进程/线程的持续时间有多长? 当请求被处理并且返回响应时它们会被销毁吗?

线程共享相同的代码。 根据定义。

进程将共享相同的代码,因为这是Apache的工作方式。

他们不是故意相互沟通的。 您的代码无法轻松确定还在进行什么操作。 这是devise。 你不能告诉你正在运行哪个进程,也不能说出在这个进程空间中运行的其他线程。

这个过程是长期的。 他们不(也不应该)dynamic创build。 您可以将Apacheconfiguration为在开始时为其分配多个并发副本,以避免创build进程的开销。

创build线程的开销要小得多。 Apaches如何在内部处理线程并不重要。 但是,您可以将Apache视为每个请求启动一个线程。

假设networking服务器只能同时支持100个请求,但是现在却有1000个并发请求。 它如何处理这种情况? 它会像队列一样处理它们,并在服务器可用时处理请求吗? 还是其他的方法?

这是“可扩展性”问题。 简言之 – 随着负载的增加,性能如何下降。 一般的答案是服务器变慢。 对于一些负载级别(比方说100个并发请求),有足够的进程可用,他们都运行得非常快。 在某些负载级别(比如说101个并发请求),它开始变慢。 在其他负载水平(谁知道多less请求),它变得如此缓慢,你不满意的速度。

有一个内部队列(作为TCP / IP工作方式的一部分),但没有pipe理员将工作负载限制为100个并发请求。 如果你得到更多的请求,创build更多的线程(不是更多的进程),而且运行速度更慢。

首先,要求所有点的详细的答案是多less,恕我直言。

无论如何,有关你的问题的几个简短的答案:

#1

这取决于服务器的体系结构。 Apache是​​一个多进程,也可以是multithreading服务器。 有一个主进程监听networking端口,并pipe理一个工作进程池(在“worker”mpm的情况下,每个工作进程有多个线程)。 当一个请求进入时,它被转发给一个空闲的工作人员。 主服务器根据负载和configuration设置启动和终止工作人员来pipe理工作人员池的大小。

现在,lighthttpd和nginx是不同的; 它们是所谓的基于事件的体系结构,其中多个networking连接通过使用对事件多路复用的操作系统支持(例如POSIX中的传统select()/ poll())复用到一个或多个工作进程/线程上,或者更具可扩展性,不幸的是,Linux中的epoll等特定于OS的机制。 这样做的好处是,每个额外的networking连接只需要几百个字节的内存,从而允许这些服务器保持打开数以万计的连接,这通常对于诸如apache之类的每个进程/线程请求结构是不可接受的。 但是,这些基于事件的服务器仍然可以使用多个进程或线程来利用多个CPU内核,并且还可以并行执行阻塞的系统调用,例如正常的POSIX文件I / O。

有关更多信息,请参阅Dan Kegel的有点过时的C10k页面 。

#2

再次,这取决于。 对于传统的CGI,每个请求都会启动一个新的进程。 对于使用apache的mod_php或mod_python,解释器被embedded到apache进程中,因此不需要启动新的进程或线程。 然而,这也意味着每个apache进程都需要相当多的内存,并且结合上面解释的#1的问题,限制了可伸缩性。

为了避免这种情况,可能会有一个独立的运行解释器的重量级进程池,当需要生成dynamic内容时,前端Web服务器可以代理到后端。 这基本上是FastCGI和mod_wsgi采用的方法(尽pipe它们使用自定义协议而不是HTTP,所以在技术上可能不是代理)。 这通常也是使用基于事件的服务器时select的方法,因为用于生成dynamic内容的代码很less是可重入的,为了在基于事件的环境中正常工作,该代码将是必需的。 如果dynamic内容代码不是线程安全的,也适用于multithreading方法; 人们可以说,前端Apache服务器与线程工人mpm代理后端Apache服务器运行PHP代码与单线程prefork MPM。

#3

根据你要求的级别,他们会通过操作系统caching机制共享一些内存,是的。 但一般来说,从程序员的angular度来看,他们是独立的。 请注意,这种独立性本身并不是一件坏事,因为它可以直接对多台机器进行水平缩放。 但是,唉,一些交stream往往是必要的。 一个简单的方法是通过数据库进行通信,假设其他原因需要通常情况下。 另一种方法是使用一些专用的分布式内存caching系统,如memcached 。

#4

依靠。 他们可能会排队,或者服务器可能会回复一些合适的错误代码,比如HTTP 503,或者服务器可能只是首先拒绝连接。 通常,根据服务器的加载情况,以上所有情况都可能发生。

#5

这种方法的可行性取决于服务器架构(请参阅我对#1的回答)。 对于一个基于事件的服务器来说,保持连接打开并不是什么大问题,但是对于apache来说,这肯定是由于每个连接都需要大量的内存。 是的,这当然需要一个长期运行的解释器过程,但如上所述,除了传统的CGI,这是非常多的。

Web服务器是multithreading环境 ; 除了使用应用程序范围variables外,用户请求不会与其他线程交互。

所以:

  1. 是的,每个用户都会创build一个新的线程
  2. 是的,HTML将被处理每个请求
  3. 您将需要使用应用程序范围的variables
  4. 如果你得到的要求比你能处理的要多,他们将被放在队列中。 如果他们在configuration的超时期限之前得到服务,用户将得到他的回应,或者像“服务器忙”一样错误。
  5. 彗星不是特定的任何服务器/语言。 你可以通过每隔n秒查询你的服务器来达到同样的结果,而不用处理其他讨厌的线程问题。

因为进程隔离是你并不总是有控制权或知识的东西,所以到目前为止我所学到的是我应该编写依赖于线程和“ 上下文 ”的代码来存储将“经典地”存储为静态数据的数据。 但即使如此,随着“ 持续”的到来,这种情况也许会在不久的将来发生改变。

这些RoR特定的链接与这个问题有关:

  • 并发是Ruby中的一个神话
  • Ruby性能是否需要彻底改变?