使用 Nginx 和 pm-static 进行服务器端优化

本文是关于构建示例应用程序(多图像库博客)以进行性能基准测试和优化的系列文章的一部分。 (在此处查看回购协议。)

让我们继续优化我们的应用程序。 我们从即时生成缩略图开始,每个请求需要 28 秒,具体取决于运行演示应用程序的平台(在我的例子中,它是主机操作系统和 Vagrant 之间的缓慢文件系统集成),并将其降低到非常可接受的 0.7 秒。

诚然,这 28 秒应该只发生在初始加载时。 调整后,我们能够实现生产就绪时间:

故障排除

假设您已经完成了引导过程并让应用程序在您的机器上运行——无论是虚拟的还是真实的。

注意:如果您在 Windows 机器上托管 Homestead Improved box,共享文件夹可能会出现问题。 这可以通过添加来解决 type: "nfs" 设置为 folderHomestead.yaml:

你也应该跑 vagrant up 如果问题仍然存在,则从具有管理权限的 shell/powershell 界面(右键单击,以管理员身份运行)。

在执行此操作之前的一个示例中,我们在每个请求上获得 20 到 30 秒的加载时间,并且无法获得比每秒一个请求更快的速率(接近每秒 0.5 个):

过程

让我们来看看测试过程。 我们在主机上安装了 Locust,并创建了一个非常简单的 locustfile.py:

from locust import HttpLocust, TaskSet, task

class UserBehavior(TaskSet):
    @task(1)
    def index(self):
        self.client.get("https://www.sitepoint.com/")

class WebsiteUser(HttpLocust):
    task_set = UserBehavior
    min_wait = 300
    max_wait = 1000

然后我们将 ngrok 下载到我们的来宾计算机并通过它建立所有 HTTP 连接,以便我们可以通过静态 URL 测试我们的应用程序。

然后我们启动 Locust 并让 100 个并行用户涌入我们的应用程序:

我们的服务器堆栈包括 PHP 7.1.10、Nginx 1.13.3 和 MySQL 5.7.19,在 Ubuntu 16.04 上。

PHP-FPM 及其进程管理器设置

php-fpm 产生自己的进程,独立于网络服务器进程。 这些进程数量的管理配置在 /etc/php/7.1/fpm/pool.d/www.conf (这里的7.1可以换成当前实际使用的PHP版本号)。

在这个文件中,我们找到 pm 环境。 这个设置可以设置为 dynamic, ondemandstatic. 动态可能是最普遍的智慧; 它允许服务器在多个设置之间调整生成的 PHP 进程的数量:

pm = dynamic
; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
; This value sets the limit on the number of simultaneous requests that will be
; served.
pm.max_children = 6
; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers = 3
; The desired minimum number of idle server processes
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.min_spare_servers = 2
; The desired maximum number of idle server proceses
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.max_spare_servers = 4

这些值的含义是不言自明的,进程的生成是按需进行的,但受这些最小值和最大值的限制。

修复 Windows 共享文件夹问题后 nfs,并使用 Locust 进行测试,我们能够在 100 个并发用户的情况下每秒获得大约 5 个请求,失败率约为 17-19%。 一旦请求蜂拥而至,服务器就会变慢,每个请求都需要十多秒钟才能完成。

然后我们改变了 pm 设置为 ondemand.

ondemand 意味着没有最小进程:一旦请求停止,所有进程都会停止。 有些人提倡这种设置,因为这意味着服务器不会在空闲状态下花费任何资源,但对于专用(非共享)服务器实例,这不一定是最好的。 生成进程包括开销,内存中获得的内容在按需生成进程所需的时间中丢失。 此处相关的设置是:

pm.max_children = 6
; and
pm.process_idle_timeout = 20s;
; The number of seconds after which an idle process will be killed.
; Note: Used only when pm is set to 'ondemand'
; Default Value: 10s

测试时,我们稍微增加了这些设置,不必担心资源不足。

还有 pm.max_requests,可以更改,它指定每个子进程在重生之前应执行的请求数。

此设置是速度和稳定性之间的权衡,其中 0 意味着无限。

ondemand 并没有带来太大的变化,除了我们注意到当我们开始用请求蜂拥而至我们的应用程序时初始等待时间更长,以及更多初始失败。 换句话说,没有大的变化:应用程序每秒能够处理大约 4 个到最多 6 个请求。 等待时间和失败率与 dynamic 设置。

然后我们尝试了 pm = static 设置,允许我们的 PHP 进程接管服务器的最大资源,交换或驱动 CPU 停止。 此设置意味着我们一直在强制系统发挥最大作用。 这也意味着——在我们服务器的限制范围内——不会有任何生成开销时间成本。

我们看到的是提高了 20%。 但是,请求失败率仍然很高,响应时间也不是很好。 该系统远未准备好投入生产。

但是在Pingdom Tools上,我们在系统没有压力的情况下得到了可以忍受的3.48秒:

这意味着 pm static 是一种提升,但是在负载更大的情况下,还是会下降。

在之前的一篇文章中,我们解释了 Nginx 本身如何充当静态和动态内容的缓存系统。 所以我们求助于 Nginx 魔法,并试图将我们的应用程序的性能提升到一个全新的水平。

我们成功了。 让我们看看如何。

Nginx 和 fastcgi 缓存

proxy_cache_path /home/vagrant/Code/ng-cache levels=1:2 keys_zone=ng_cache:10m max_size=10g inactive=60m;
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
fastcgi_cache_path /home/vagrant/Code/ngd-cache levels=1:2 keys_zone=ngd_cache:10m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_use_stale error timeout invalid_header http_500;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
add_header NGINX_FASTCGI_CACHE $upstream_cache_status;

server {
    listen 80;
    listen 443 ssl http2;
    server_name nginx-performance.app;
    root "/home/vagrant/Code/project-nginx/public";

    index index.html index.htm index.php;

    charset utf-8;

    proxy_cache ng_cache;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/nginx-performance.app-error.log error;

    sendfile off;

    client_max_body_size 100m;

    location ~ .php$ {
        fastcgi_split_path_info ^(.+.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        fastcgi_intercept_errors off;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;


      fastcgi_cache ngd_cache;
      fastcgi_cache_valid  60m;
    }

    location ~ /.ht {
        deny all;
    }

    ssl_certificate     /etc/nginx/ssl/nginx-performance.app.crt;
    ssl_certificate_key /etc/nginx/ssl/nginx-performance.app.key;
}

我们打开我们的 Nginx 虚拟主机文件并添加上述设置。 让我们解释一下。

proxy_cache_path /home/vagrant/Code/ng-cache levels=1:2 keys_zone=ng_cache:10m max_size=10g inactive=60m;

正如 Apache 与 Nginx 性能:优化技术中所解释的, proxy_cache_path 用于缓存静态资产——如图像、样式表、JavaScript 文件。 路径本身需要存在; 我们需要创建这些目录。 levels 指定该路径/文件夹内目录的深度。 遍历对于请求时间来说可能是昂贵的,所以最好保持小。 Keys zone 是一个名字; 每个虚拟主机都可以(也应该)使用一个单独的主机。 Max size 表示缓存的最大大小,inactive 表示时间项目将保留在缓存中,即使它们没有被请求。

在这段不活动时间之后,将重新填充资源的缓存。

proxy_cache_use_stalefastcgi_cache_use_stale 很有趣,因为它们可以提供“始终在线”的功能,我们可以在 Cloudflare 等 CDN 提供商中看到:如果后端离线,Nginx 将从缓存中提供这些资源。 这种失败在一定程度上证明了我们的网站。

一切 fastcgi_cache_* 设置用于 PHP 生成的(动态)内容,以及 proxy_cache_* 设置是针对静态文件的。

fastcgi_cache_key 定义用于缓存的键。

fastcgi_ignore_headers 禁用处理来自 FastCGI 后端的某些响应标头字段。

我们可以使用另一个有趣的设置:

fastcgi_cache_purge

这定义了能够清除缓存的请求。 Nginx(它的 ngx_http_fastcgi_module) 为我们提供了相当全面的缓存工具集。 使用上述指令的一个例子是:

fastcgi_cache_path /data/nginx/cache keys_zone=cache_zone:10m;

map $request_method $purge_method {
    PURGE   1;
    default 0;
}

server {
    ...
    location / {
        fastcgi_pass        backend;
        fastcgi_cache       cache_zone;
        fastcgi_cache_key   $uri;
        fastcgi_cache_purge $purge_method;
    }
}

在这里,PURGE REST 请求将能够从缓存中删除内容。

在某些情况下也可以重新验证缓存。

在我们的配置中,我们没有使用 Nginx 的所有复杂性和功能,但如果我们需要它们,知道它们在那里是很好的。

我们在响应中添加了 Nginx 标头,以便能够判断资源是否来自缓存:

add_header NGINX_FASTCGI_CACHE $upstream_cache_status;

然后,我们可以检查和剖析页面加载时间,看看哪些有效,哪些无效:

要预热缓存,我们需要遍历每个资源的请求。

fastcgi_cache_methods 可用于缓存特定请求方法,如 POST。 GET 和 HEAD 默认被缓存。

还有字节范围缓存,可用于视频流优化,如此处所述。

使用 Nginx 提供的所有可配置性,可以轻松设计一个完整的私有 CDN 网络。

为我们网站的静态和动态内容启用上述配置后,我们启动了 Locust,并让我们的系统拥有 100 个并行用户。 结果的差异简直令人惊讶。 服务器以前承受的压力现在感觉不到了。

我们可以看到每个请求的平均时间为 170 毫秒。 那是大约一百倍的改进。 每秒请求数超过 100。

我们还可以看到,在平均响应时间图表中,初始请求的响应时间出现峰值,之后,响应时间下降得越来越快,降至 130 毫秒左右。

Nginx 缓存给我们带来了一些很大的改进。 此应用程序的主要瓶颈不会是硬件资源,即使它们是适度的。

我们还可以看到失败请求的百分比从 17% 上升到 0.53%。

然后我们去Pingdom的页面测试,测试了我们的网站:

我们可以看到我们设法将页面加载时间控制在一秒以下!

我们还测试了单个画廊页面,其中包含相关和最新画廊的额外“行李”:

我们附上此测试的 HAR 文件报告以供分析。

结论

在本文中,测试了我之前关于 Nginx 性能的讨论中涉及的一些要点,并讨论和分析了进程管理等其他设置及其对页面加载时间影响的度量。

我们错过了什么值得一提的事吗? 你能想到我们可以应用于此应用程序以提高性能的其他 Nginx 设置吗?

阅读更多

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注