项目

通用

个人资料

操作

优化FastCGI性能

概述

如果你最近问过自己

  • “我的负载需要多少个PHP后端?”或
  • “为什么我的应用程序会时不时地返回500错误?”

那么你会想非常仔细地阅读这篇文章。

已过时

Note: The info herein was last updated in 2012 and is outdated.
The current (2021) recommendation for PHP performance is to use "PHP-FPM":https://php-fpm.org/about/ (FastCGI Process Manager), which is available as a package in many OS distros.

我需要多少个PHP进程?

这就是你在寻找答案的问题,要回答它,最简单的方法可能是使用一个例子。

lighty 正在管理一个管道。一端是使用网页浏览器的用户,另一端是PHP。
如果传入请求多于后端所能处理的数量,lighty 会将它们排队并推送新的
请求到再次空闲的后端。
如果请求过多导致队列满了,它就会爆发,接下来对该后端的请求将被拒绝,你会在错误日志中看到类似这样的消息

  ... load = 380 ...

要计算你需要的后端数量,请考虑以下几点

  • 你每秒有100个PHP请求
  • PHP端的平均请求时间是0.1秒

在平均情况下,你需要

100 PHP requests/sec * 0.1sec/PHP request = 10 PHP processes

由于你可能无法控制传入的PHP请求数量,你最好的做法是减少平均
在PHP进程中花费的请求时间。一些建议

测量平均请求时间并非易事,因此像“fastcgi.backend.0.load: 22”这样的输出是一个指示器
表明当前会使用多少个PHP进程。

测量负载

加载status-module并启用统计信息: :

  server.modules = {..., "mod_status", ... }

  status.statistics-url = "/server-counters" 

计数器页面列出了FastCGI模块的几个计数器

  • 模块处理的请求总数
  • 等待处理的请求总数
  • 每个后端等待处理的请求数量

注意:

如果你有多个后端,你应该单独命名每个后端。 :

    fastcgi.server = ( 
      ".php" => (
        "backend1" => ( "host" => "php-srv1", ... ),
        "backend2" => ( "host" => "php-srv2", ... ),
      )
    ) 

你可能会得到这样的输出

  fastcgi.active-requests: 22
  fastcgi.backend.0.0.connected: 5639
  fastcgi.backend.0.0.died: 0
  fastcgi.backend.0.0.disabled: 0
  fastcgi.backend.0.0.load: 11
  fastcgi.backend.0.0.overloaded: 0
  fastcgi.backend.0.1.connected: 7724
  fastcgi.backend.0.1.died: 0
  fastcgi.backend.0.1.disabled: 0
  fastcgi.backend.0.1.load: 11
  fastcgi.backend.0.1.overloaded: 0
  fastcgi.backend.0.load: 22
  fastcgi.requests: 13363

我们有2个后端(max-procs = 2),当前负载为22(fastcgi.backend.0.load: 22)。负载是平均的
分布在两个后端上(fastcgi.backend.0.0.load: 11, fastcgi.backend.0.1.load: 11)。

使用rrdtool监控负载

在你的配置中启用mod_rrdtool,并添加以下配置条目(请根据你的实际设置进行调整)

   rrdtool.binary = "/usr/bin/rrdtool" 
   rrdtool.db-name = "/var/www/lighttpd/lighttpd-web.rrd" 

阅读rrd的文档,了解如何从.rrd文件生成图表。

安装XCache

XCache是缓存器之一,如果你使用PHP,它可以加速FastCGI处理。

http://trac.lighttpd.net/xcache/wiki/ReleaseArchive 选择最新版本。

要安装

  ~/src $ wget http://... (the release url)
  ~/src $ tar -zxf xcache-*.tar.gz
  ~/src $ cd xcache
  ~/src/xcache $ phpize
  ~/src/xcache $ ./configure --enable-xcache --enable-xcache-coverager
  ~/src/xcache $ make
  ~/src/xcache $ su
  ~/src/xcache # make install
  ~/src/xcache # cat xcache.ini >> /etc/php.ini
  ~/src/xcache # $EDITOR /etc/php.ini

设置 xcache.size=64M,并设置你的 xcache.admin.pass。

设置Web界面

  alias.url += ("/xcache-admin/" => "/usr/share/xcache/admin/")

通过将浏览器指向 http://localhost/xcache-admin/ 进行检查

调优数据库

这是MySQL调优的一个非常简短的介绍。

注意:在更改服务器上的任何内容之前,请先查阅资料,特别是如果你没有专用的MySQL服务器且内存不足的情况下。

首先检查 my.cnf

  [mysqld]
  ## default is 100, might need to raise it

  max-connections = 200
  ## if you use innodb alot, increase the pool-size 
  ## default is 8M, far too low.

  innodb_buffer_pool_size = 512M

  ## for MyISAM it is 
  key_buffer_size = 128M

  query_cache_size = 32M

  ## logs are good

  log-slow-queries

  long-query-time = 2

  log-queries-not-using-indexes

重启MySQL服务器并在数据目录中检查'hostname-slow.log'。它将列出所有满足以下条件的查询:

  • 执行时间超过2秒,或
  • 未使用索引

对所有这些查询,运行 EXPLAIN 并在必要时添加索引。你需要重点关注那些满足以下条件的查询:

  • 运行频繁,或
  • 查询时间 > 2

如果检查的行数比发送的行数大好几倍,请添加索引。

另一件需要检查的事情是

  mysql> SHOW GLOBAL STATUS;
  ...
  | Created_tmp_disk_tables | 88    |
  | Created_tmp_files       | 2     |
  | Created_tmp_tables      | 39079 |
  ...

Created_tmp_disk_tables 应该尽可能小,相对于 Created_tmp_tables
  | Com_select              | 39004 |
  | Select_scan             | 39004 |

糟糕,所有 SELECT 语句都是全表扫描(Table-Scans),这非常糟糕。
  ...
  | Table_locks_immediate   | 3     |
  | Table_locks_waited      | 0     |

这很好,我们从未需要等待表锁。

注意:在MySQL 5.0.x之前的版本中,它是 SHOW STATUS。

基准测试

通过测量优化前后的响应时间,你可以了解负载增加将如何
在未来影响你。

有几种工具可用于测量响应时间

  • ab/ab2 是 Apache 服务器软件包的一部分
    • 最多处理 1024 个连接
    • 只对单个URL进行压力测试
    • 基于 select()
  • flood 是一个 Apache 项目
  • siege
    • 处理大约 100 个并行连接(在因内存不足而崩溃之前)
    • 是多线程的
    • 可以生成随机负载
  • http_load
    • 可以生成随机负载
    • 允许节流
  • httpperf

当你使用只查询单个URL多次的基准测试工具时,你不会发现由以下原因引起的问题:

  • 脏缓存(MySQL查询缓存,字节码缓存等)
  • 锁定(表锁,文件锁等)

如果你使用 siege 进行随机负载测试,使用 ab 进行单个URL负载测试,你应该能获得有用的结果。

(待续)

PHP进程是否会过多?

PHP进程过多也可能不好。如果你拥有的进程数量超过所需,PHP进程将耗尽所有可用内存
并开始使用你的交换空间,这比实际内存慢得多。

如果你使用这样的配置

  fastcgi.server = ( ".php" =>
      (( "socket" => "/tmp/php-fastcgi.socket",
          "bin-path" => "/usr/bin/php-cgi",
          "max-procs" => 10,
          "bin-environment" => (
              "PHP_FCGI_CHILDREN" => "16",
              "PHP_FCGI_MAX_REQUESTS" => "1000" 
          ),
          "broken-scriptfilename" => "enable" 
      ))
  )

你将拥有(根据著名的公式)

  num-procs = max-procs * ( 1 + PHP_FCGI_CHILDREN ) 

  10 * (16 + 1) = 170 procs

一个运行 APC 3.0.11 的 PHP 5.1.5 进程本身占用约 13MB (RSZ) 内存

  $ ps axu | grep php
    400 web       16   0  152m  13m 6804 S    1  0.7   0:01.99 php-fcgi     

  13MB * 170 processes = 2,16GB RAM

并非所有进程都会从一开始就占用13MB,但你开始看到问题所在。进入交换空间会适得其反。

为什么我的PHP应用程序会时不时地返回500错误?

这个问题似乎源于PHP一个鲜为人知的问题:PHP在处理500个请求后会停止接受新的FastCGI连接;不幸的是,在PHP清理代码期间存在一个潜在的竞争条件,PHP可能正在关闭但套接字仍然打开,因此 lighty 可以将第501个请求发送给PHP并使其“被接受”,但随后PHP似乎只是退出,导致 lighty 返回500错误。

为限制这种情况发生,请将 PHP_FCGI_MAX_REQUESTS 设置为 500。

此外,在配置 lighty 管理 php-fcgi 进程时,最好拥有更多进程和更少子进程,而不是更少进程和更多子进程。例如

  fastcgi.server = ( ".php" =>
      (( "socket" => "/tmp/php-fastcgi.socket",
          "bin-path" => "/usr/bin/php-cgi",
          "max-procs" => 10,
          "bin-environment" => (
              "PHP_FCGI_CHILDREN" => "10",
              "PHP_FCGI_MAX_REQUESTS" => "500" 
          ),
          "broken-scriptfilename" => "enable" 
      ))
  )

优于

  fastcgi.server = ( ".php" =>
      (( "socket" => "/tmp/php-fastcgi.socket",
          "bin-path" => "/usr/bin/php-cgi",
          "max-procs" => 2,
          "bin-environment" => (
              "PHP_FCGI_CHILDREN" => "50",
              "PHP_FCGI_MAX_REQUESTS" => "500" 
          ),
          "broken-scriptfilename" => "enable" 
      ))
  )

因为当一个后端“死亡”时,该FastCGI进程的所有子进程都将变得不可用。拥有多个进程意味着如果其中一个死亡,其余进程可以分担负载。如果你有2个进程并且两者负载相等,那么如果其中一个死亡,另一个将突然过载并自行崩溃,导致 lighty 抛出500错误。

注意:如果你在使用带有字节码缓存的PHP,每个进程都会使用自己的字节码缓存。因此,如果你的 max-procs 为2,就会有2个字节码缓存占用内存。在这种情况下,最好将 max-procs 设置为1,并使用 PHP_FCGI_CHILDREN 来扩展PHP进程的数量,并希望后端不会“死亡”。

远程负载均衡

如果所有这些都不起作用,你仍然可以使用几台服务器,安装一个像NFS这样的共享文件系统并运行
PHP在这些服务器上。只需将它们的IP添加到 fastcgi.server 设置中的“host”字段,lighty 将
在PHP服务器之间均衡负载。

我使用Perl、Ruby、Python或其他语言

PHP仅作为示例使用。这些建议中的大多数也适用于其他语言。

gstrauss 更新于 将近4年前 · 31次修订