2023拉

调优LAMP应用程序的5种简单方法:优化您的 PHP 设置

上一篇 / 下一篇  2011-08-24 17:43:04

调优LAMP应用程序的5 种简单方法

简介

Wikipedia、Facebook 和 Yahoo! 等主要 web 属性使用 LAMP 架构来为每天数百万的请求提供服务,而 Wordpress、Joomla、Drupal 和 SugarCRM 等 web 应用程序软件使用其架构来让组织轻松部署基于 web 的应用程序。

该架构的优势在于其简单性。而 .NET 这样的堆栈和 Java™ 技术可能使用大量硬件、昂贵的软件栈和复杂的性能调优,LAMP 堆栈可以运行于商品硬件之上,使用开源软件栈。由于软件栈是一个松散的组件集,而非一个整体堆栈,性能调优是一大挑战,因为需要分析和调优每个组件。

然而,这有几个个简单性能任务会对任何规模的网站的性能产生巨大的影响。在本文中,我们将探讨旨在优化 LAMP 应用程序性能的 5 个这样的任务。这些项目应当很少需要对您的应用程序进行架构更改,使其成为最大化您的 web 应用程序所需的响应能力和硬件需求的安全、便捷的选择。

优化您的 PHP 设置

虽然实现操作码缓存是性能改进的一大创举,不过也有大量其他优化选项可供您基于 php.ini 文件中的设置优化您的 PHP 设置。这些设置更适合于生产实例;在开发或测试实例上,您可能不希望做这些变更,因为它会使得应用程序问题的调试变得更难。

让我们看一下对于性能提升很重要的一些项目。

应当禁用的选项

有若干 php.ini 设置应当予以禁用,因为它们常用作向后兼容性:

•register_globals — 在 PHP V4.2 之前该功能常常是默认值,其中传入的请求变量被自动赋给普通 PHP 变量。这样做除了引起重大安全问题之外(使未过滤的传入请求数据与普通 PHP 变量内容相混),对每一个请求这样做还会产生开销。因此禁用这一设置使您的应用程序更安全且能提高性能。

•magic_quotes_* — 这是 PHP V4 的另一遗留项,其中传入的数据会自动避开有风险的表单数据。它旨在作为一个安全特性,在将传入的数据发送到数据库之前对其进行整理,但不是很有效,因为它不能帮助用户预防常见的 SQL 注入攻击。由于大部分数据库层支持能更好地处理该风险的准备语句,禁用该设置会再次消除这个烦人的性能问题。

•always_populate_raw_post_data — 这仅当您出于某些原因需要查看传入的未过滤 POST 数据的整个负载时才需要。否则,它仅在内存中存储 POST 数据的一个副本,而这没有必要。

然而,在遗留代码上禁用这些选项会有风险,因为它们可能取决于其设置来实现正确执行。不应当基于被设置的这些选项来开发任何新代码,而且可能的话,您应当寻求方法来重构您的现有代码,避免使用它们。

应当禁用或调整设置的选项

您可以启用 php.ini 文件的一些优秀性能选项,来提升您的脚本速度:

•output_buffering — 您应当确保启用该选项,因为它会以块为单位将输出刷回到浏览器,而非以每个 echo 或 print 语句为单位,而后者会大大减缓您的请求响应时间。

•variables_order — 这个指令控制传入请求的 EGPCS(Environment、Get、Post、Cookie 和 Server)变量解析顺序。如果您没有使用某种超全局变量(比如环境变量),您可以安全地删除它们来获得一点加速,从而避免在每一个请求上解析它们。

•date.timezone — 这是在 PHP V5.1 中添加的一个指令,用于设置默认时区,然后用于后面将要介绍的 DateTime 函数。如果您不在 php.ini 文件中设置该选项,PHP 会执行大量系统请求来弄清它是什么,且在 PHP V5.3 中,对每一个请求会发出一个警告。

就以应当在您的生产实例上配置的设置而言,这些被看作是 “唾手可得”。就 PHP 而言,还有一件事需要考虑。这就是您的应用程序中 require() 和 include()(以及其同级 require_once() 和 include_once())的使用。这些函数优化您的 PHP 配置和代码,以防止对每个请求进行不必要的文件状态检查,从而减少响应时间。

管理require和include

从性能来看,文件状态调用(即为检查一个文件是否存在而对底层文件系统进行的调用)相当昂贵。文件状态的最大元凶之一以 require() 和 include() 语句的形式出现,这两个语句用于将代码带到脚本中。require_once() 和 include_once() 的同级调用更成问题,因为它们不仅需要验证文件是否存在,而且它之前没有包含在内。

那么解决这个问题的最好方式是什么?您可以做一些事来加快解决。

•为所有 require() 和 include() 调用使用绝对路径。这将使 PHP 更清楚您希望包含的确切文件,因此无需为您的文件检查整个 include_path。

•保持 include_path 中的条目数较低。这在很难为每个 require() 和 include() 调用提供绝对路径的情况(通常在大型遗留应用程序中会出现这种情况)下很有用,方法就是不检查您包含的文件不在的位置。

APC 和 Wincache 还有用于缓存 PHP 进行的文件状态检查结果的机制,因此无需进行反复的文件系统检查。当您将 include 文件名保留为静态而非变量驱动的时,它们最有效,因此尽可能尝试这样做很有用。

优化您的数据库

数据库优化很快会成为一个前沿话题,我几乎没有空间在这里完全公正地做这个话题。但是如果您在寻求优化您的数据库的速度,首先应当采取一些步骤,这应当对常见问题有所帮助。

将数据库放在自己的机器上

数据库查询自身可以变得相当激烈,通常在对大小合理的数据集执行简单的 SELECT 语句时限定在 100% 的 CPU。如果您的 web 服务器和数据库服务器都在竟用单一机器上的 CPU 时间,这无疑将减慢您的请求速度。因此我想第一步最好是将 web 服务器和数据库服务器放在单独的机器上,确保您的数据库服务器是两者中更强健的(数据库服务器喜欢大量内存和多个 CPU)。

合理设计和编制表索引

数据库性能的最大问题可能源自于不良数据库设计和缺失索引。SELECT 语句通常是运行在典型 web 应用程序中的最常见的查询类型。它们也是在数据库服务器上运行的最耗时的查询。此外,这些类型的 SQL 语句对适当的索引和数据库设计最敏感,因此查看以下指示,获取实现最优性能的技巧。

•确保每个表都有一个主键。这为表提供一个默认顺序和快速方式来联接其他表。

•确保一个表中的任何外键(即链接记录到另一个表中的记录的键)的索引得到合理编制。许多数据库会自动对这些键施加约束,以便值真正匹配另一个表中的一条记录,这有助于摆脱这一困难。

•试图限制一个表中的列数。一个表中有太多列比仅有一些列时进行查询所需的扫描时间要长。此外,如果您有不常用的含多个列的一个表,您也在通过 NULL 值字段浪费磁盘空间。文本或 blob 等可变大小字段也是如此,其中表大小的增长可以远超过需求。在这种情况下,您应当考虑将其他栏分成不同的表,在记录的主键上将其联合起来。

分析在服务器上运行的查询

改进数据库性能的最佳方法是分析在您的数据库服务器上运行什么查询,且运行它们需要多长时间。几乎每个数据库都有具有这种功能的工具。对于 MySQL,您可以利用慢查询日志来查找有问题的查询。要使用它,在 MySQL 配置文件中将 slow_query_log 设置为 1,然后将 log_output 设置为 FILE,将它们记录到文件 hostname-slow.log 中。您可以设置 long_query_time 阈值,确定查询必须运行多少秒才被看作是 “慢查询”。我想建议将该阈值首先设置为 5 秒,随着时间的推移将其缩减为 1 秒,具体取决于您的数据集。如果您探究该文件,您会看到类似于清单 1 的详细查询。

清单 1. MySQL 慢查询日志

  1. /usr/local/mysql/bin/mysqld, Version: 5.1.49-log, started with:  
  2.  
  3. Tcp port: 3306 Unix socket: /tmp/mysql.sock  
  4.  
  5. Time Id Command Argument  
  6.  
  7. # Time: 030207 15:03:33  
  8.  
  9. # User@Host: user[user] @ localhost.localdomain [127.0.0.1]  
  10.  
  11. # Query_time: 13 Lock_time: 0 Rows_sent: 117 Rows_examined: 234  
  12.  
  13. use sugarcrm;  
  14.  
  15. select * from accounts inner join leads on accounts.id = leads.account_id;  
  16.  

我们想要考虑的关键对象是 Query_time,显示查询需要的时间。另一项要考虑的是 Rows_sent 和 Rows_examined 的数量,因为这些可指这样的情况:其中如果一个查询察看太多行或返回太多行,就会被错误地书写。您可以更深入地钻研如何写查询,即在查询开始处加上 EXPLAIN,它会返回查询计划,而非结果集,如清单 2 所示。

清单 2. MySQL EXPLAIN 结果

  1. mysql> explain select * from accounts inner join leads on accounts.id = leads.account_id;  
  2.  
  3. +----+-------------+----------+--------+--------------------------+---------+---  
  4.  
  5. | id | select_type | table | type | possible_keys  
  6.  
  7. | key | key_len | ref | rows | Extra |  
  8.  
  9. +----+-------------+----------+--------+--------------------------+---------+--------  
  10.  
  11. | 1 | SIMPLE | leads | ALL | idx_leads_acct_del | NULL | NULL  
  12.  
  13. | NULL | 200 | |  
  14.  
  15. | 1 | SIMPLE | accounts | eq_ref | PRIMARY,idx_accnt_id_del | PRIMARY | 108  
  16.  
  17. | sugarcrm.leads.account_id | 1 | |  
  18.  
  19. +----+-------------+----------+--------+--------------------------+---------+---------  
  20.  
  21. 2 rows in set (0.00 sec)  
  22.  

MySQL 手册更深入探究 EXPLAIN 输出的主题(参见 参考资料),但是我考虑的一项重要内容是 ‘type’ 列为 ‘ALL’ 的地方,因为这需要 MySQL 做一个全表扫描,且不需要键来执行查询。这些帮助您在添加索引时会大幅提高查询速度。

 


TAG:

 

评分:0

我来说两句

Open Toolbar