Apache监控之编译时配置

Apache监控之编译时配置
编译时的配置主要涉及到的设置有:MPM配置、模块、原子操作、mod_status、多socket性况下串行accept、单socket性况下串行accept、延迟关闭、Scoreboard文件和DYNAMIC_MODULE_LIMIT。

  1. MPM配置
    Apache 2.x支持插入式并行处理模块,称为多路处理模块(MPM Multi-Processing Modules),在编译Apache时必须选择一个MPM(有且只能选择一次),针对非UNIX系统的MPM有:beos、 mpm_netware、mpmt_os2、mpm_winnt。对于UNIX系统,有几个不同的MPM可供选择,它们都会影响到httpd的速度和可伸缩性:
    Ø preforkMPM使用多个子进程,但每个子进程并不包含多线程,每个进程只处理一个链接,在许多系统上它的速度和workerMPM一样快,但是需要更多的内存。
    Ø workerMPM使用多个子进程,每个子进程中又有多个线程,每个线程处理一个请求。该MPM通常对高流量的服务器是一个不错的选择,因为它比preforkMPM需要更少的内存且更具有伸缩性。
    Apache Prefork MPM 是个多路处理模块(MPM)实现了一个非线程型的、预派生的web服务器,它的工作方式类似于Apache 1.3。它适合于没有线程安全库,需要避免线程兼容性问题的系统。它是要求将每个请求相互独立的情况下最好的MPM选择方式,这样若一个请求出现问题就不会影响到其他请求。
    这个MPM具有很强的自我调节能力,只需要很少的配置指令调整。最重要的是将MaxClients设置为一个足够大的数值以处理潜在的请求高峰,同时又不能太大,以致需要使用的内存超出物理内存的大小。
    其工作方式是一个单独的控制进程(父进程)负责产生子进程,这些子进程用于监听请求并作出应答。Apache总是试图保持一些备用的(spare)或者是空闲的子进程用于迎接即将到来的请求,这样客户端就不需要在得到服务前等候子进程的产生。
    StartServers、MinSpareServers、MaxSpareServers、MaxClients指令用于调节父进程如何产生子进程。通常情况下Apache具有很强的自我调节能力,所以一般的网站不需要调整这些指令的默认值,可能需要处理最大超过256个并发请求的服务器可能需要增加MaxClients的值。内存比较小的机器则需要减少MaxClients的值以保证服务器不会崩溃。
    在Unix系统中,父进程通常以root身份运行以便邦定80端口,而Apache产生的子进程通常以一个低特权的用户运行,User和Group指令用于设置子进程的低特权用户。运行子进程的用户必须要对它所服务的内容有读取的权限,但是对服务内容之外的其他资源必须拥有尽可能少的权限。MaxRequestsPerChild指令控制服务器杀死旧进程产生新进程的频率。
    MaxSpareServers
    该指令设置空闲子进程的最大数量。所谓空闲子进程是指没有正在处理请求的子进程。如果当前有超过MaxSpareServers数量的空闲子进程,那么父进程将杀死多余的子进程。只有在非常繁忙机器上才需要调整这个参数,此参数设的通常不能设置过大。如果你将该指令的值设置为比MinSpareServers小,Apache将会自动将其修改成“MinSpareServers+1”。
    MinSpareServers
    该指令设置空闲子进程的最小数量。所谓空闲子进程是指没有正在处理请求的子进程,如果当前空闲子进程数少于MinSpareServers ,那么Apache将以最大每秒一个的速度产生新的子进程。只有在非常繁忙机器上才需要调整这个参数,不应将此参数设置过大。
    MaxClients
    该指令设置了允许同时伺服的最大接入请求数量。任何超过MaxClients限制的请求都将进入等候队列,直到达到ListenBacklog指令限制的最大值为止,一旦一个链接被释放,队列中的请求将得到服务。
    对于非线程型的MPM(也就是prefork),MaxClients表示可以用于伺服客户端请求的最大子进程数量,默认值是256,要增大这个值,必须同时增大ServerLimit的值。
    对于线程型或者混合型的MPM(也就是beos或worker),MaxClients表示可以用于伺服客户端请求的最大线程数量。线程型的beos的默认值是50,对于混合型的MPM默认值是16(ServerLimit)乘以25(ThreadsPerChild)的结果。因此要将MaxClients增加到超过16个进程才能提供的时候,必须同时增加ServerLimit的值。
    MaxClients计算公式如下:
    MaxClients ≈ (RAM - size_all_other_processes)/(size_apache_process)
    如以下实例:
    系统:VPS(虚拟专用服务器)Linux RedHat5 128MB RAM
    Apache:2.2 mpm_prefork、mod_php、mod_rewrite、mod_ssl及其它一些模块
    其他服务:MySQL、BIND、Sendmail
    报告的系统内存:120MB
    报告的httpd进程大小:7-13MB
    假设Apache可用内存为:90MB
    最佳的设置:
    StartServers 5
    MinSpareServers 5
    MaxSpareServers 10
    ServerLimit 15
    MaxClients 15
    MaxRequestsPerChild 2000
    ServerLimit
    该指令指服务器允许配置的进程数上限,对于preforkMPM,这个指令设置了MaxClients最大允许配置的数值。对于workerMPM,这个指令和ThreadLimit结合使用设置了MaxClients最大允许配置的数值。任何在重启期间对这个指令的改变都将被忽略,但对MaxClients的修改却会生效。
    使用这个指令时要特别注意,如果将ServerLimit设置成一个高出实际需要许多的值,将会有过多的共享内存被分配,如果将ServerLimit和MaxClients设置成超过系统的处理能力,Apache可能无法启动,或者系统将变得不稳定。
    对于preforkMPM,只有在需要将MaxClients设置成高于默认值256的时候才需要使用这个指令,要将此指令的值保持和MaxClients一样。
    对于workerMPM,只有在需要将MaxClients和ThreadsPerChild设置成需要超过默认值16个子进程的时候才需要使用这个指令。不要将该指令的值设置的比MaxClients 和ThreadsPerChild需要的子进程数量高。
    注意:Apache在编译时内部有一个硬限制“ServerLimit 20000”(对于preforkMPM为“ServerLimit 200000”),不能超越这个限制。
    StartServers
    该指令是指服务器启动时建立的子进程数。StartServers指令设置了服务器启动时建立的子进程数量。因为子进程数量动态的取决于负载的轻重,所有一般没有必要调整这个参数。不同的MPM默认值也不一样,对于worker默认值是“3”,对于prefork默认值是“5”,mpmt_os2是“2”。
    Apache MPM worker多路处理模块(MPM)使网络服务器支持混合的多线程多进程。由于使用线程来处理请求,所以可以处理海量请求,而系统资源的开销小于基于进程的MPM,但是,它也使用了多进程,每个进程又有多个线程,以获得基于进程的MPM的稳定性。
    控制这个MPM的最重要的指令是,控制每个子进程允许建立的线程数的ThreadsPerChild指令,和控制允许建立的总线程数的MaxClients。
    工作方式是每个进程可以拥有的线程数量是固定的,服务器会根据负载情况增加或减少进程数量,一个单独的控制进程(父进程)负责子进程的建立,每个子进程可以建立ThreadsPerChild数量的服务线程和一个监听线程,该监听线程监听接入请求并将其传递给服务线程处理和应答。
    Apache总是试图维持一个备用(spare)或是空闲的服务线程池,这样,客户端无须等待新线程或新进程的建立即可得到处理。初始化时建立的进程数量由StartServers指令决定,随后父进程检测所有子进程中空闲线程的总数,并新建或结束子进程使空闲线程的总数维持在MinSpareThreads和MaxSpareThreads所指定的范围内。由于这个过程是自动调整的,几乎没有必要修改这些指令的缺省值。可以并行处理的客户端的最大数量取决于MaxClients指令,活动子进程的最大数量取决于MaxClients除以ThreadsPerChild的值。
    有两个指令设置了活动子进程数量和每个子进程中线程数量的硬限制,要想改变这个硬限制必须完全停止服务器然后再启动服务器(直接重启是不行的),ServerLimit是活动子进程数量的硬限制,它必须大于或等于MaxClients除以ThreadsPerChild的值,ThreadLimit是所有服务线程总数的硬限制,它必须大于或等于ThreadsPerChild指令,这两个指令必须出现在其他workerMPM指令的前面。
    在设置的活动子进程数量之外,还可能有额外的子进程处于“正在中止”的状态但是其中至少有一个服务线程仍然在处理客户端请求,直到到达MaxClients以致结束进程,虽然实际数量会很小。这个行为能够通过以下禁止特别的子进程中止的方法来避免:
    Ø 将MaxRequestsPerChild设为“0”;
    Ø 将MaxSpareThreads和MaxClients设为相同的值;
    Apache Winnt MPM 是专门针对Windows NT优化的MPM(多路处理模块),它使用一个单独的父进程产生一个单独的子进程,在这个子进程中轮流产生多个线程来处理请求,也就是说 mpm_winnt只能启动父子两个进程,不能像Linux下那样同时启动多个进程。
    mpm_winnt主要通过ThreadsPerChild和MaxRequestsPerChild两个参数来优化Apache。
    关于StartServers、MinSpareServers、MaxSpareServers、MaxClients和ServerLimit的设置与Apache Prefork MPM模块中的设置一致。
    ThreadsPerChild
    这个参数用于设置每个进程的线程数,子进程在启动时建立这些线程后就不再建立新的线程了. 一方面因为mpm_winnt不能启动多个进程,所以这个数值要足够大,以便可以处理可能的请求高峰;另一方面该参数以服务器的响应速度为准的,数目太大的反而会变慢。因此需要综合均衡一个合理的数值。
    mpm_winnt上的默认值是64,最大值是1920,建议设置为100-500之间,服务器性能高的话值大一些,反之值小一些。
    MaxRequestsPerChild
    该参数表示每个子进程能够处理的最大请求数,即同时间内子进程数目。设置为零表示不限制, mpm_winnt上的默认值就是0。
    注意:建议不应该设置为0,主要基于两点考虑:第一:可以防止(偶然的)内存泄漏无限进行,从而耗尽内存;第二:给进程一个有限寿命,从而有助于当服务器负载减轻的时候减少活动进程的数量。
  2. 模块
    既然内存用量是影响性能的重要因素,就应当尽量去除不需要的模块,如何将模块编译成DSO,取消不必要的模块就是一件非常简单的事情,只需要注释掉LoadModule指令中不需要的模块。
    如果已经将模块静态链接进Apache二进制核心,就必须重新编译Apache并去掉不想要的模块。增减模块牵涉到的一个问题是,究竟需要哪些模块、不需要哪些模块?这取决于服务器的具体情况。一般说来,至少要包含下列模块:mod_mime、mod_dir、mod_log_config。也可以不要mod_log_config,但是一般不推荐这样做。
  3. 原子操作
    一些模块,比如mod_cache和worker使用APR(Apache可移植运行时)的原子API,这些API提供了能够用于轻量级线程同步的原子操作。
    默认情况下,APR在每个目标OS/CPU上使用其最有效的特性执行这些操作。比如许多现代CPU的指令集中有一个原子的比较交换(compare-and-swap,CAS)操作指令,在一些老式平台上,APR默认使用一种缓慢的、基于互斥执行的原子API以保持对没有CAS指令的老式CPU的兼容。如果只打算在新式的CPU上运行Apache,那么可以在编译时使用–enable-nonportable-atomics 选项:
    ./buildconf
    ./configure --with-mpm=worker --enable-nonportable-atomics=yes
    –enable-nonportable-atomics选项只和下列平台相关:
    Ø SPARC上的Solaris,认情况下,APR使用基于互斥执行的原子操作。如果你使用 --enable-nonportable-atomics 选项,APR将使用SPARC v8plus操作码来加快基于硬件的CAS操作。注意,这仅对UltraSPARC CPU有效。
    Ø x86上的Linux默认情况下,APR在Linux上使用基于互斥执行的原子操作。如果使用 --enable-nonportable-atomics选项,APR将使用486操作码来加快基于硬件的CAS操作。注意,这仅对486以上的CPU有效。
  4. mod_status
    如果Apache在编译时包含了mod_status,而且在运行时设置了“ExtendedStatus On”,那么Apache会对每个请求调用两次gettimeofday()(或者根据操作系统的不同,调用times())以及(1.3版之前)几个额外的time()调用,使状态记录带有时间标志。为了得到最佳性能,可以设置“ExtendedStatus off”(这也是默认值)。
  5. 多socket情况下的串行accept
    需要注意Unix socket API的一个缺点,假设web服务器使用了多个Listen语句监听多个端口或者多个地址,Apache会使用select()以检测每个socket是否就绪,select()会表明一个socket有零或至少一个连接正等候处理,由于Apache的模型是多子进程的,所有空闲进程会同时检测新的连接。
    目前至少有两种解决方案:
    第一:是使用非阻塞型socket,不阻塞子进程并允许它们立即继续执行。但是这样会浪费CPU时间,设想一下,select有10个子进程,当一个请求到达的时候,其中9个被唤醒,并试图accept此连接,继而进入select循环,并且其间没有一个子进程能够响应出现在其他socket上的请求,直到退出select循环。总之,除非有很多的CPU,而且开了很多子进程。
    第二:是Apache所使用的方案是,使内层循环的入口串行化。
  6. 单socket情况下的串行accept
    上述对多socket的服务器进行的讲述,那么对单socket的服务器又怎样呢?理论上似乎应该没有什么问题,因为所有进程在连接到来的时候可以由accept()阻塞,而不会产生进程“饥饿”的问题,但是在实际应用中,它掩盖了与上述非阻塞方案几乎相同的问题。按大多数TCP栈的实现方法,在单个连接到来时,内核实际上唤醒了所有阻塞在accept的进程,但只有一个能得到此连接并返回到用户空间,而其余的由于得不到连接而在内核中处于休眠状态。这种休眠状态为代码所掩盖,但的确存在,并产生与多socket中采用非阻塞方案相同的负载尖峰的浪费。
    同时,发现在许多体系结构中,即使在单socket的情况下,实施串行化的效果也不错,因此在几乎所有的情况下,都这样处理了。在Linux(2.0.30,双Pentium pro 166/128M RAM)下的测试显示,对单socket,串行化比不串行化每秒钟可以处理的请求少了不到3%,但是,不串行化对每一个请求多了额外的100ms的延迟, 此延迟可能是因为长距离的网络线路所致,并且仅发生在LAN中。如果需要改变对单socket的串行化,可以定义SINGLE_LISTEN_UNSERIALIZED_ACCEPT,使单socket的服务器彻底放弃串行化。
  7. Scoreboard 文件
    Apache父进程和子进程通过scoreboard进行通讯,通过共享内存来实现是最理想的,在曾经实践过或者提供了完整移植的操作系统中,都使用共享内存,其余的则使用磁盘文件。磁盘文件不仅速度慢,而且不可靠。
    注意:在对Linux的Apache1.2移植版本之前,没有使用内存共享,此失误使Apache的早期版本在Linux中表现很差。