Apache HTTP Server 是一个免费的开源 WEB 服务端软件。最早基于 NCSA HTTPd server,Apache 在 WWW 的最初成长过程中扮演了十分重要的角色,很快就接替 NCSA HTTPd 成为占统治地位的 HTTP server,一直到 1996 年它都是最流行的软件。

Apache 入门

Apache 支持大量的功能,许多都以预编译模块的形式部署,扩展了内核的功能。支持的范围从服务端编程语言的支持直到验证方案的实现。一些通常的语言接口支持 Perl、Python、Tcl、PHP。流行的验证模块包括 mod_accessmod_authmod_digestmod_auth_digest 等。其它功能如 SSL 和 TLS、代理模块、URL 重写模块、自定义日志文件等。

客户端、服务端、URL

网址用 URL 来表示,Uniform Resource Locator。由协议、服务器名称、URL 路径组成,有时在最后还会跟有查询字符串,用来给服务器传递更多的参数。

网络客户端会使用特定的协议连接到服务器,用 URL 发出一个使用资源的 请求

URL 路径可以是服务器上的文件、处理器、程序文件等。

服务器会发送一个 回复,由状态码和回复内容组成,其中回复内容为可选的。状态码用来表示客户端的请求是否成功,如果失败,则代表具体是什么错误。

期间的细节以及错误都会写进日志文件。

主机名与 DNS

为了连接到服务器,客户端首先会把服务器的主机名解析成 IP 地址,因此,若希望服务器可被访问,其主机名必须存在于 DNS 中。

多个主机名可以指向同一个 IP 地址,多个 IP 地址可以指向同一个物理服务器。因此通过使用虚拟主机,可以在同一个物理服务器上同时运行多个网站。

如果服务器仅用于测试,不需要互联网访问,可以把主机名放到 hosts 文件中,以实现本地解析。如:

127.0.0.1  www.example.com

hosts 文件通常位于 /etc/hosts

配置文件与其指令

Apache HTTP Server 是使用文本文件来配置的,配置文件通常为 /etc/httpd/conf/httpd.conf

该配置文件经常会分割成几个小配置文件,为的是更易于管理。在 httpd.conf 中,可以使用 Include 指令来加载这些小文件。管理员可以根据自己的需要自行切割、使用这些小文件。

通过在这些配置文件中放置配置专用指令来配置服务器。一个指令由一个关键字及随后的一个或多个参数组成。

如何放置这些指令:

  • 如果是全局的设置,应该放在主配置文件 httpd.conf 中,而且要独立于任何段落之外。
  • 如果只应用于特定的目录,应该放在特定的 <Directory> 段落中。

除了主配置文件之外,在网站内容目录中还有一个 .htaccess 文件,其中也包含了一些指令。该文件是为那些无法直接访问主配置文件的人准备的。

网站内容

网站内容可以有多种形式,大致可分为静态内容和动态内容。

静态内容

静态内容如 HTML 文件,图片文件,CSS 文件,及其文件系统中其它文件。

DocumentRoot 指令用于指定文件系统中哪个目录做为网站根目录。该指令可以全局指定,也可以针对每个虚拟主机指定。

如果没有指定其它文件名,请求访问目录时,服务器会把当前目录的 index.html 文件做为回复。

动态内容

动态内容是在请求时动态生成的,而且每个请求所产生的内容可能有所不同。生成动态内容有多种方法,有多种处理器可以使用。

有许多第三方的应用程序可以在 Apache HTTP Server 中使用,像 mod_php 这样的第三方模块可以用来编写代码。

日志文件

对于 Apache HTTP Server 的管理员,最有价值的资产就是日志文件,尤其是错误日志。只要排错就必须要使用错误日志。

ErrorLog 指令用于指定错误日志文件。

错误日志的条目会告诉你发生了什么错误,什么时间发生的。经常还会告诉你如何修复。每个错误日志消息都会包含一个错误代码,需要时可以用来在网上搜索更详尽的解决办法。

可以通过设置,让错误日志包含用户的日志 ID,这样在排错时就便于将错误日志的条目与访问日志的条目关联在一起,可以了解是哪个请求引起的错误。

多处理模块

Multi-Processing Module,MPM

本节内容非本人翻译,原文引用官方文档。做了微调。

介绍

Apache HTTP Server 被设计为一个功能强大,并且灵活的 web 服务器, 可以在很多平台与环境中工作。不同平台和不同的环境往往需要不同的特性,或可能以不同的方式实现相同的特性最有效率。Apache httpd 通过模块化的设计来适应各种环境。这种设计允许网站管理员通过在编译时或运行时,选择哪些模块将会加载在服务器中,从而确定服务器的具体功能。

Apache HTTP Server 2.0 将此模块化设计扩展到 web 服务器最基本的功能上。它提供了可以选择的多处理模块,负责绑定到网络端口上、接受请求、调度子进程处理请求。

将模块化设计扩展到服务器的这个级别,为我们带来两大好处:

  • Apache httpd 能更优雅,更高效率的支持不同的平台。尤其是 Apache httpd 的 Windows 版本现在更有效率了,因为 mpm_winnt 能使用原生网络功能来取代 Apache httpd 1.3 中使用的 POSIX 层。它也可以扩展到其它平台来使用专用的 MPM。
  • Apache httpd 能更好的为有特殊要求的站点定制。例如,要求更高伸缩性的站点可以选择使用线程的 MPM,即 worker 或 event; 需要可靠性或者与旧软件兼容的站点可以使用 prefork。

在用户看来,MPM 很像其它 Apache httpd 模块。主要区别在于,任何时候,必须有一个,而且 只能有一个 MPM 加载到服务器中。可用的 MPM 列表位于模块索引页面。

默认 MPM

下表列出了不同系统的默认 MPM。如果你不在编译时手动选择,那么它就是你将要使用的 MPM。

Linux : preforkworkerevent 之一

Windows :mpm_winnt

在 Linux 中,最终决定安装哪个 MPM 是基于两个问题:

  1. 系统是否支持线程?
  2. 系统是否支持线程安全轮询(thread-safe polling),具体地说是 kqueueepoll 函数?

如果两个问题的答案均为 “是”,则默认 MPM 为 event

如果问题一的答案为 “是”,问题二的答案为 “否”,则默认 MPM 为 worker

如果两个问题的答案均为 “否”,则默认 MPM 为 prefork

在实际运行中,意味着几乎默认值总会是 event,因为当前所有操作系统均支持这两个功能。

构建 MPM 为静态模块

在全部平台中,MPM 都可以构建为静态模块。在构建时选择一种 MPM,链接到服务器中。如果要改变 MPM,必须重新构建。

为了使用指定的 MPM,请在执行 configure 脚本 时,使用参数 --with-mpm=NAME。NAME 为指定的 MPM 名称。

编译完成后,可以使用 ./httpd -l 来确定选择的 MPM。 此命令会列出编译到服务器程序中的所有模块,包括 MPM。

构建 MPM 为动态模块

在 Unix 或类似平台中,MPM 可以构建为动态模块,与其它动态模块一样在运行时加载。构建 MPM 为动态模块,就可以通过修改 LoadModule 指令内容来改变 MPM,而无需重新构建服务器程序。

LoadModule mpm_prefork_module modules/mod_mpm_prefork.so

在执行 configure 脚本时,使用 --enable-mpms-shared 选项可以启用此特性。当给出的参数为 all 时,所有此平台支持的 MPM 模块都会被安装。还可以在参数中给出模块列表。

默认 MPM,可以自动选择或者在执行 configure 脚本时通过 --with-mpm 选项来指定,然后出现在生成的服务器配置文件中。 编辑 LoadModule 指令内容可以选择不同的 MPM。

Apache 的启动

在 Linux 中,httpd 程序做为服务(daemon)运行,在后台负责处理请求。

Apache 是如何启动的

如果配置文件中 Listen 指定的端口默认为 80,或是其它低于 1024 的端口,则必须有 root 权限才能启动 Apache,才能绑定到该端口。一旦服务端启动,并进行了一些准备活动,如打开日志文件等,服务端会生成几个子进程,这些子进程用来负责侦听来自客户端的请求,而 httpd 这个主进程继续以 root 身份运行,但子进程是以较低权限的用户运行的,这些均由所选择的多处理模块控制。

建议使用 apachectl 脚本来间接调用 httpd 可执行文件。该脚本会为 httpd 设定必要的环境变量,以保存在特定的操作系统中正常运行,然后再调用 httpd 二进制文件。apachectl 会把所有命令行的参数传递给 httpd,因此 httpd 所有的选项都可以配合 apachectl 使用。还可以直接编辑 apachectl 脚本中顶部的 HTTPD 变量,以指定 httpd 二进制文件的正确位置,同时还可以设定希望持续生效的任何命令行参数。

apachectl 是 Apache 的前端工具,用于帮助管理员控制服务的运行。apachectl 脚本可以两种模式运行。其一,单纯做为 httpd 的前端,来设置一些必要的环境变量,然后调用 httpd,把命令行参数传递给它。其二,可以做为 SysV 启动脚本,接受单一参数如 startrestartstop,将之翻译为对应的信号给 httpd。

httpd 被调用时所做的第一件事,是找到并读取配置文件。配置文件的位置是在编译时确定的,但运行时也可以通过 -f 选项来单独指定:

/usr/local/apache2/bin/apachectl -f /usr/local/apache2/conf/httpd.conf

如果启动时一切正常,服务端会从终端分离开来,命令提示符几乎立即就会返回,这表示服务端已经启动并运行,此时可以用浏览器来连接服务端,查看网页根目录的测试页面了。

启动期间的错误

如果 Apache 启动期间遇到严重问题,在退出之前,它会把问题的具体信息输出到终端或错误日志。最常见的错误消息是 Unable to bind to Port ...,原因通常为:

  • 没有用 root 登陆,导致启动服务端时无权打开端口
  • 在启动服务端之前,已经有 Apache 的其它实例,或其它服务端占用了该端口

随系统自动启动

若想在系统启动时能够自动启动该服务,可以在 rc.local 文件中调用 apachectl start,这将会以 root 身份启动 Apache,前提是一定要确认服务器的安全和访问限制都被正确配置。

apachectl 脚本是按照标准 SysV 初始脚本来设计的,可以接受 startrestartstop 这些参数,然后将其翻译为对应的信号给 httpd。因此,虽然可以简单地把 apachctl 链接到初始目录,但一定要检查当前系统在启动方面的需求。

关于停止 Apache

要想停止或重启 Apache,必须给运行中的 httpd 进程发送一个信号,有两种方法:

方法一

可以使用 kill 命令来直接给进程发信号,你会发现系统中同时有多个 httpd 进程,但应该只给 父进程 发信号,其进程 ID 保存在 PidFile 指令所设定的文件中,也就是说只需要给父进程发信号就足够了。

有四种信号可以给父进程发送:TERMUSER1HUPWINCH

kill -TERM `cat /usr/local/apache2/logs/httpd.pid`

方法二

第二种方法是使用 apchectl -k,后面可以接 stoprestartgracefulgraceful-stop

在发送信号之后,可以通过错误日志来查看进度:

tail -f /usr/local/apache2/logs/error_log
立即停止

Stop Now.

发送的信号:TERM

apachectl -k stop

给父进程发送 GERMstop 信号会使其立即去尝试杀掉所有子进程,有可能会需要几秒钟来完成。然后父进程自己也会退出。这期间产生的任何请求都会被终止,不再接受任何请求。

优雅重启

Greaceful Restart.

发送的信号:USR1

apachectl -k graceful

收到 USR1graceful 信号的父进程会劝告子进程尽快退出:把当前请求处理完就马上退出,如果当前没有任何工作要处理则立即退出。

父进程会重新读取配置文件,并重新打开日志文件。每死一个子进程,父进程都会用新一代配置文件生成新的子进程来替换,该子进程立即进入接受新请求的状态。

整个重启的过程始终会遵守 MPM 的进程控制指令的要求,用于服务于客户端请求的进程、线程的数量会 始终保持 在一个合适的值。进一步说,整个过程是严格遵守 StartServers 的限制:

如果一秒钟之后,至少有一个新的 StartServers 新的子进程没有被创建,则新建足够的子进程来填补空缺。因为该机制不仅会针对服务器的负载而尝试维护子进程的正确数量,同时还会尊重 StartServers 参数所设定的限制。

StartServers 指令用于指定启动服务端时需创建多少个子进程。

如果使用了 mod_status 模块就会注意到,发送 USR1 信号以后,服务端的数量并没有归零,该机制不仅是为了减少服务端无法处理请求的时间,同时也是为了尊重你的参数设置。(重启期间发生的请求会被操作系统排进队列,以保证不会丢失。)为了实现这个目的,系统必须一代一代地持续追踪所有子进程。

状态模块会使用 G 来表示那些优雅重启之前已经开始处理请求的子进程。

当前,对于使用 USR1 的日志滚动脚本来说,它无法知晓是否所有记录预重启的子进程都已经结束了。建议在发送 USR1 信号之后,使用一种适合的延时机制,然后再对旧日志进行处理。

执行重启时,系统会首先检查配置文件的语法,如果发现错误会报错,服务端会拒绝重启。要想检查配置文件是否有语法错误,可以先尝试用非 root 用户来启动 httpd,如果没有语法错误,它就会尝试打开其套接字及日志,然后就会失败,因为不是 root,或因为当前运行的 httpd 已经占用了端口。如果因为其它原因而失败,就可能是配置文件的语法问题。

立即重启

Restart Now.

发送的信号:HUP

apachectl -k restart

给父进程发送 HUPrestart 信号会像 TERM 一样杀掉子进程,但父进程不会退出。

父进程会重新读取配置文件,重新打开所有日志文件,然后再生成一组新的子进程,继续运行服务。

如果使用了 mod_status 模块就会注意到,发送 HUP 信号以后,服务端的数量被归零。

优雅停止

Graceful Stop.

发送的信号:WINCH

apachectl -k graceful-stop

收到 WINCHgraceful-stop 信号的父进程会劝告子进程尽快退出:把当前请求处理完就马上退出,如果当前没有任何工作要处理则立即退出。

之后,父进程会删除其 PidFile 文件,停止对所有端口的侦听。

父进程会继续运行,并监督子进程对请求的处理进度。一旦所有子进程都终结并退出,或超时(GracefulShutdownTimeout 指令所限定的时间)了,父进程也会退出。如果超时,所有剩余的子进程都会收到 TERM 信号,被强制退出。

处于这种优雅(graceful)状态时,如果收到 TERM 信号,会立即终止父进程及其所有子进程。然而,此时 PidFile 应该已经被删除了,无法使用 apachectlhttpd 来发送 TERM 信号。

信号 graceful-stop 允许我们同时运行多个相同配置的 httpd 实例,在进行 httpd 优雅升级时这是一个强大的功能,但是,使用有些配置时,它也会造成死锁及竞争。 无论是锁文件(Mutex)还是套接字文件(ScriptSock)都包含了服务端的 PID,按理说应该和平共处。但是,如果一个配置指令、第三方模块或持续的 CGI 使用了磁盘中的任何其它的锁文件或状态文件,就需要一些工作以确保 httpd 的多个运行实例不会损坏彼此的文件。 同时,还要注意其它的潜在竞争危险,如使用 rotatelogs 风格的管道日志。rotatelogs 的多个运行实例会尝试同时滚动相同的日志文件,这会损坏各自的日志文件。