优化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进程中花费的请求时间。一些建议
- 使用字节码缓存,例如 http://xcache.lighttpd.net/ 或 http://pecl.php.net/APC
- 为你的应用程序添加缓存
- 调优数据库查询
测量平均请求时间并非易事,因此像“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仅作为示例使用。这些建议中的大多数也适用于其他语言。