Nginx:高性能 Web 服务器和反向代理
Apache 是最流行的 Web 服务器,也是有史以来最成功的开源项目之一。自 1996 年 4 月以来,Apache 服务过的网站比任何其他 Web 服务器都多。世界上许多最大的网站,包括 YouTube、Facebook、维基百科和 Craigslist,都使用 Apache 每月处理数十亿的页面浏览量。多年来,Apache 已经证明自己是一个非常稳定、安全和可配置的 Web 服务器。尽管 Apache 是一款出色的 Web 服务器,但如果有一种替代方案,它具有相同的功能、更简单的配置和更好的性能,会怎么样呢?这种 Web 服务器确实存在,它就是 Nginx。
Nginx,发音为“Engine X”,是一款高性能 Web 服务器和反向代理。它由 Igor Sysoev 为俄罗斯第二大网站 www.rambler.ru 创建。Rambler 自 2004 年夏天开始使用 Nginx,目前每天处理约 5 亿个请求。与 Apache 一样,Nginx 也被美国一些最大的网站使用,包括 WordPress (#26)、YouPorn (#27)、Hulu 和 MochiMedia。截至 2008 年 5 月,Nginx 是第四大最受欢迎的 Web 服务器,目前为超过 200 万个网站提供服务。由于它仅次于 Apache、IIS 和 GFE,因此它实际上是 Linux 可用的第二大最受欢迎的 Web 服务器。
与 Apache 一样,Nginx 具有您期望从领先的 Web 服务器获得的所有功能
静态文件服务。
SSL/TLS 支持。
虚拟主机。
反向代理。
负载均衡。
压缩。
访问控制。
URL 重写。
自定义日志记录。
服务器端包含。
WebDAV。
FLV 流媒体。
FastCGI。
它稳定、安全且非常易于配置,正如您将在本文后面看到的那样。但是,Nginx 相对于 Apache 的主要优势在于性能和效率。
我使用 ab(Apache 的基准测试工具)对 Nginx v0.5.22 和 Apache v2.2.8 进行了简单的测试。在测试期间,我使用 vmstat 和 top 监控了系统。结果表明,在服务静态内容时,Nginx 的性能优于 Apache。两个服务器在并发数为 100 时表现最佳。Apache 使用了四个工作进程(线程模式),30% 的 CPU 和 17MB 的内存,每秒处理 6,500 个请求。Nginx 使用了一个工作进程,15% 的 CPU 和 1MB 的内存,每秒处理 11,500 个请求。
Nginx 能够以更少的资源每秒处理更多请求,这归功于其架构。它由一个主进程组成,该主进程将工作委托给一个或多个工作进程。每个工作进程使用来自 Linux 内核(epoll/select/poll)的特殊功能,以事件驱动或异步方式处理多个请求。这使 Nginx 能够以极小的开销快速处理大量并发请求。Apache 可以配置为每个请求使用一个进程(pre-fork)或为每个请求使用一个线程(worker)。尽管 Apache 的线程模式比其 pre-fork 模式性能好得多,但它仍然比 Nginx 的事件驱动架构使用更多的内存和 CPU。
Nginx 在大多数 Linux 发行版中都可用。在本文中,我使用 Ubuntu 8.04 (Hardy),它包含 Nginx 版本 0.5.33。如果您的发行版没有 Nginx,或者您想运行更新的版本,您可以随时下载最新的稳定版本(在撰写本文时为 v0.6.31)并从源代码安装。
以 root 身份运行以下命令来安装 Nginx
# apt-get install nginx
现在 Nginx 已经安装完成,您可以使用启动脚本来启动、停止或重启 Web 服务器
# /etc/init.d/nginx start # /etc/init.d/nginx stop # /etc/init.d/nginx restart
大多数配置更改不需要重启,在这种情况下,您可以使用 reload 命令。通常,在重新加载之前测试 Nginx 配置文件是否有错误是一个好主意
# nginx -t # /etc/init.d/nginx reload
让我们继续启动服务器
# /etc/init.d/nginx start
现在 Nginx 应该在您的机器上运行了。如果您在浏览器中打开 http://127.0.0.1/,您应该看到一个带有“Welcome to nginx!”的页面。
现在 Nginx 已经安装完成,让我们看一下它的配置文件,位于 /etc/nginx/nginx.conf。此文件包含 Nginx 的服务器范围设置,它应该看起来类似于这样
user www-data; worker_processes 1; error_log /var/log/nginx/error.log; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; access_log /var/log/nginx/access.log; sendfile on; keepalive_timeout 65; tcp_nodelay on; gzip on; include /etc/nginx/sites-enabled/*; }
我们不会更改任何这些设置,但让我们讨论其中一些设置,以帮助我们了解 Nginx 的工作原理。worker_processes 设置告诉 Nginx 启动多少个子进程。如果您的服务器有多个处理器或正在执行大量的磁盘 IO,您可能需要尝试增加此数字,看看是否能获得更好的性能。worker_connections 设置限制每个工作进程的并发连接数。要确定最大并发请求数,您只需将 worker_processes 乘以 worker_connections。
error_log 和 access_log 设置指示默认的日志记录位置。您也可以在每个站点的基础上配置这些设置,正如您将在本文后面看到的那样。与 Apache 一样,Nginx 配置为以 www-data 用户身份运行,但您可以使用 user 设置轻松更改此设置。Nginx 的启动脚本需要知道主进程的进程 ID,该 ID 存储在 /var/run/nginx.pid 中,如 pid 设置所示。
sendfile 设置允许 Nginx 使用特殊的 Linux 系统调用,以非常有效的方式通过网络发送文件。gzip 选项指示 Nginx 压缩每个响应,这会占用更多 CPU,但可以节省带宽并缩短响应时间。此外,Nginx 还提供了另一个压缩模块,称为 gzip 预压缩(从 0.6.24 版本开始可用)。此模块在同一位置查找扩展名为 .gz 的文件的压缩副本,并将其提供给启用 gzip 的客户端。这避免了每次请求时都必须压缩文件。
我们关心的最后一个设置是 sites-enabled 目录的 include 指令。在 /etc/nginx 内部,您会看到另外两个目录,/etc/nginx/sites-available 和 /etc/nginx/sites-enabled。对于您想要使用 Nginx 托管的每个网站,您应该在 /etc/nginx/sites-available 中创建一个配置文件,然后在 /etc/nginx/sites-enabled 中创建一个指向您创建的配置文件的符号链接。主要的 Nginx 配置文件包含 /etc/nginx/sites-enabled 中的所有文件。这有助于组织您的配置文件,并使启用和禁用特定网站变得非常容易。
现在我们已经介绍了主要的配置文件,让我们为一个基本的网站创建一个配置文件。在我们开始之前,我们需要禁用 Ubuntu 为我们创建的默认站点
# rm -f /etc/nginx/sites-enabled/default
现在,创建一个名为 /etc/nginx/sites-available/basic 的新配置文件,内容如下
server { listen 127.0.0.1:80; server_name basic; access_log /var/log/nginx/basic.access.log; error_log /var/log/nginx/basic.error.log; location / { root /var/www/basic; index index.html index.htm; } }
创建根目录和 index.html 文件
# mkdir /var/www/basic # cd /var/www/basic # echo "Basic Web Site" > index.html
启用站点并重启 Nginx
# cd /etc/nginx/sites-enabled # ln -s ../sites-available/basic . # /etc/init.d/nginx restart
如果您在浏览器中打开 http://127.0.0.1/,您应该看到一个带有“Basic Web Site”的页面。正如您所看到的,使用 Nginx 创建新站点非常容易。
让我们回顾一下我们创建的新配置文件。server 指令用于定义一个新的虚拟服务器,其所有设置都用大括号括起来。listen 指令指示此服务器将接受请求的 IP 和端口,server_name 设置虚拟服务器的主机名。正如我之前提到的,access_log 和 error_log 设置可以在每个站点的基础上设置。通常,为每个站点提供自己的一组日志文件是一个好主意。
接下来是 location 指令,它允许您修改站点不同部分的设置。在我们的例子中,我们只有一个用于整个站点的位置。但是,您可以有多个 location 指令,并且可以使用正则表达式来定义它们。在我们的 location 块内,我们还有两个其他指令:root 和 index。root 指令用于定义此位置的文档根目录。这意味着对 /img/test.gif 的请求将在 /var/www/localhost/img/test.gif 中查找文件。最后,index 指令告诉 Nginx 将哪些文件用作此位置的默认文件。
一些网站,例如在线商店,需要安全通信 (HTTPS) 来保护信用卡交易和客户信息。与 Apache 一样,Nginx 通过 SSL 模块支持 HTTPS,并且非常容易设置。
首先,您需要生成 SSL 证书。openssl 命令会询问您一堆问题,但您可以简单地为每个问题按 Enter 键
# apt-get install openssl # mkdir /etc/nginx/ssl # cd /etc/nginx/ssl # openssl req -new -x509 -nodes -out server.crt -keyout server.key
创建一个名为 /etc/nginx/sites-available/secure 的新配置文件,其中包含以下内容
server { listen 127.0.0.1:443; server_name secure; access_log /var/log/nginx/secure.access.log; error_log /var/log/nginx/secure.error.log; ssl on; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; location / { root /var/www/secure; index index.html index.htm; } }
创建根目录和 index.html 文件
# mkdir /var/www/secure # cd /var/www/secure # echo "Secure Web Site" > index.html
启用站点并重启 Nginx
# cd /etc/nginx/sites-enabled # ln -s ../sites-available/secure . # /etc/init.d/nginx restart
如果您在浏览器中打开 https://127.0.0.1/(注意 https),您可能会收到一条关于无法验证证书的警告。那是因为我们在这个例子中使用了自签名证书。继续并告诉您的浏览器接受证书,您应该看到一个带有“Secure Web Site”的页面。
此配置文件与我们之前的配置非常相似,但有一些不同之处。首先,请注意这个新服务器正在监听端口 443,这是 HTTPS 的标准端口。其次,我们使用以下行启用了 SSL 模块ssl on;。如果您自己编译 Nginx 而不是使用 Ubuntu 包,您需要确保您指定了--with-http_ssl_module当您运行./configure; 否则,SSL 模块将不可用。第三,我们使用 ssl_certificate 和 ssl_certificate_key 指令指向我们之前创建的证书和密钥。
在许多情况下,您会希望从单个服务器运行多个网站。这称为虚拟主机,Nginx 支持基于 IP 和基于名称的虚拟主机。
让我们创建两个虚拟主机:one.example.com 和 two.example.com。首先,我们需要在我们的 /etc/hosts 文件中添加一行,以便 one.example.com 和 two.example.com 指向我们的服务器(通常您会使用 DNS 执行此操作)
# echo "127.0.0.1 one.example.com two.example.com" >> /etc/hosts
现在,我们需要为每个站点创建一个配置文件。首先,创建一个名为 /etc/nginx/sites-available/one 的文件,内容如下
server { listen 127.0.0.1:80; server_name one.example.com; access_log /var/log/nginx/one.access.log; error_log /var/log/nginx/one.error.log; location / { root /var/www/one; index index.html index.htm; } }
然后,复制该文件并命名为 /etc/nginx/sites-available/two,并将每个出现的“one”替换为“two”
# cd /etc/nginx/sites-available # cp one two # sed -i "s/one/two/" two
创建根目录和 index.html 文件
# mkdir /var/www/{one,two} # echo "Site 1" > /var/www/one/index.html # echo "Site 2" > /var/www/two/index.html
启用站点并重启 Nginx
# cd /etc/nginx/sites-enabled # ln -s ../sites-available/one . # ln -s ../sites-available/two . # /etc/init.d/nginx restart
如果您在浏览器中打开 http://one.example.com/,您应该看到一个带有“Site 1”的页面。对于 http://two.example.com/,您应该看到“Site 2”。
我们刚刚通过更改 server_name 指令创建了两个在 127.0.0.1 上运行的基于名称的虚拟主机。对于基于 IP 的虚拟主机,只需更改 listen 指令,为每个站点使用不同的 IP。
现在,继续禁用这两个虚拟主机
# rm -f /etc/nginx/sites-enabled/one # rm -f /etc/nginx/sites-enabled/two # /etc/init.d/nginx restart
当您完成操作后,不要忘记删除我们添加到 /etc/hosts 的行。
除了是一个极其快速的静态 Web 服务器之外,Nginx 还是一个负载均衡器和反向代理。负载均衡器是一种用于将工作分散到多个服务器或进程的设备,而反向代理是一个透明地将请求移交给另一个服务器的服务器。除其他事项外,这允许 Nginx 处理静态内容的请求,并将动态内容的请求负载均衡到许多不同的后端服务器或进程。
对于此示例,让我们创建一个非常简单的 Python Web 服务器来提供一些动态内容。如果您不熟悉 Python,请不要担心;我们只是使用它来显示一个 Web 页面,指示服务器正在哪个端口上运行。将以下内容保存到一个名为 /tmp/server.py 的文件中
import sys,BaseHTTPServer as B class Handler(B.BaseHTTPRequestHandler): def do_GET(self): self.wfile.write("Served from port %s" % port) def log_message(self, *args): pass if __name__ == '__main__': host,port = sys.argv[1:3] server = B.HTTPServer((host,int(port)), Handler) server.serve_forever()
现在我们可以启动两个本地服务器,每个服务器在不同的端口上
# python /tmp/server.py 127.0.0.1 8001 & # python /tmp/server.py 127.0.0.1 8002 &
如果您在浏览器中打开 http://127.0.0.1:8001/,您应该看到“Served from port 8001”,如果您打开 http://127.0.0.1:8002/,您应该看到“Served from port 8002”。
现在,创建一个名为 /etc/nginx/sites-available/proxy 的新配置文件,其中包含以下内容
upstream python_servers { server 127.0.0.1:8001; server 127.0.0.1:8002; } server { listen 127.0.0.1:8000; server_name proxy; access_log /var/log/nginx/proxy.access.log; error_log /var/log/nginx/proxy.error.log; location / { proxy_pass http://python_servers; } }
启用站点并重启 Nginx
# cd /etc/nginx/sites-enabled # ln -s ../sites-available/proxy . # /etc/init.d/nginx restart
如果您在浏览器中打开 http://127.0.0.1:8000/,您应该看到一个带有“Served from port 8001”或“Served from port 8002”的页面,并且每次刷新页面时都应该交替显示。
让我们回顾一下其中一些新设置。upstream 块定义了一组后端服务器的名称。在我们的例子中,我们定义了一个名为 python_servers 的组,其中包含我们在端口 8001 和 8002 上启动的两个本地 Python 服务器。然后,我们将 Nginx 配置为使用以下行将所有请求移交给我们的后端服务器proxy_pass http://python_servers;。Nginx 以轮询方式自动负载均衡到每个 Python 服务器的请求。您还可以为每个后端设置权重,以便您可以将更多或更少的请求定向到特定服务器。
Nginx 自动处理后端故障,并将停止向失败的后端服务器发送请求,直到它再次开始响应。为了演示这一点,我们可以杀死在端口 8001 上运行的 Python 进程。使用 jobs 命令查找在端口 8001 上运行的 Python 进程的作业号,并使用kill %<作业号>杀死进程
# jobs # kill %1
在浏览器中打开 http://127.0.0.1:8000/ 并不断刷新页面,您应该只看到“Served from port 8002”页面。Nginx 检测到端口 8001 的后端服务器没有响应,因此它停止向该服务器发送请求。现在,重启端口 8001 的 Python 进程
# python /tmp/server.py 127.0.0.1 8001 &
继续刷新页面,您应该看到您的浏览器再次开始在“Served from port 8001”和“Served from port 8002”之间交替显示。Nginx 检测到端口 8001 后端正在响应,并开始向其发送请求。
资源
Nginx 网站: wiki.codemongers.com/Main
模块比较索引: wiki.codemongers.com/NginxModuleComparisonMatrix
用户评价: wiki.codemongers.com/NginxWhyUseIt
WordPress 上的 Nginx: barry.wordpress.com/2008/04/28/load-balancer-update
使用 Nginx 的 Facebook 应用程序: highscalability.com/friends-sale-architecture-300-million-page-view-month-facebook-ror-app
Will Reese 在过去的十年中一直从事 Linux 工作,主要负责扩展运行在 Apache、Python 和 PostgreSQL 上的 Web 应用程序。他喜欢在桌上足球和 Wii 网球中击败 Cory Wright。