Oracle + PHP 简明手册

通过持久连接提高性能


作者:John Coggeshall

持久数据连接的概念对许多 PHP 开发人员来说很陌生。那到底什么时候才应当使用它们呢?

本文相关下载:
 Oracle 数据库 10g 快捷版
 Zend Core for Oracle
 Apache Web Server 1.3.x

2006 年 2 月发表

多年来我一直担任专门研究 PHP 的技术顾问,在此期间经常有人问我持久数据库连接的适当性,到底问过多少次我已经记不清了。总之,很多最流行的 PHP 数据库扩展都能够建立这样的连接。

要真正了解这个问题的答案,您需要了解 PHP 脚本执行的生命周期以及 Web 服务器对该生命周期的影响 - 而这正是本文的切入点。

Apache 和 PHP 服务器如何发出请求

最常见的 PHP 设置是 PHP 以模块形式在 Apache Web 服务器(通常为 Apache 1.3.x,但 PHP 也可以在最新版本的 Apache 中运行,前提是您应在预分叉模式下运行更新的 Apache 2.0+ 版)中运行。PHP 必须在预分叉模式下运行这个表面上无关的事实却是了解持久数据库连接优缺点的框架。不明白我的意思?我们来看看 Apache 的工作方式吧。

当诸如 Web 浏览器这样的客户端连接到由 Apache 提供服务的网站时,将分派一个进程来处理该请求。在 Apache 1.3.x 中,这些进程(称作“子”进程)是相互独立的。也就是说,与基于线程的模型不同,每个进程在服务器中都有一个完全独立的内存和执行空间。由于该方法使整个服务器非常稳定(如果特殊请求导致 Apache 崩溃,那么将很有可能只导致处理该请求的特殊单个子进程崩溃),因此长期以来一直被视为有益于 Apache。

图 1 演示了该进程,图中的红色表示特殊 HTTP 请求导致的分段故障。

 

图 1
图 1 发生分段故障

尽管该体系结构显然有利于确保一个稳定的 Web 服务器环境,但每个独立 HTTP 请求的这个“沙箱”结构却具有负面影响,即与该请求有关的所有内容也必须与其他请求分隔开。由于大多数建议的 PHP 设置是将 PHP 作为模块 (mod_php) 在 Apache 中运行 实现的,因此在此预分叉模型中运行的 PHP 脚本本身也将受到相互孤立的请求的影响。因此,PHP 中的所有可用变量和资源将在单个 Apache 子进程的环境中分配和销毁 - 而这正是持久 Oracle 连接的用途所在。

由于所有 PHP 资源都在单个 Apache 子进程的环境中定义,因此当 PHP 与 Oracle 数据库建立数据库连接时,该连接将只位于该特定子进程中。因此,当执行为多个不同客户端同时建立数据库连接的同一 PHP 脚本时,每个子进程必须建立各自的连接。(参见图 2。)

 

图 2
图 2 跨多个客户端的并发数据库连接

如果每个子进程执行同一 PHP 脚本,那么您可以想象预分叉 Apache 模型将导致 PHP 以及 Oracle 效率很低,这是因为从理论上而言,所有四个子进程只需要一个数据库连接。此外,即使使用 Oracle API 为 PHP 提供的标准连接方法建立四个单独的数据库连接,将仍针对每个请求中断并重新建立此连接。即使每个子进程必须拥有自己的连接,在大多数情况下为每个请求重新建立连接也显得非常没有必要 - 因此,PHP 尝试通过持久数据库连接这个概念来解决此问题。遗憾的是,该方法只能解决部分问题 - 尽管持久连接会防止针对每个请求中断连接,但在请求之间持久保存的 PHP 资源仍然只存在于单个子进程中。

(提示:如果您很想知道可能建立的 Oracle 数据库连接总数,则查看 Apache Web 服务器中的 MaxClients 配置指令。假设 PHP 脚本针对每个请求创建 X 个数据库连接,那么 Oracle 将准备在高峰负载情况下接受 MaxClients * X 个数据库连接。)

阻止 Apache 中断数据库连接

了解了 PHP、Apache 和 Oracle 如何交互后,您现在可以充分利用每个数据库连接了,在针对每个请求处理数据库时建立一个数据库连接通常是最昂贵的操作。

乍看来,除了在单个子进程中持久保存连接以外,提高性能的方法似乎并不多。但这样的假设是错误的 - 诀窍不在于优化 PHP,而是在于优化服务器体系结构本身。

除非您正在运行只处理 RPC 请求的某种远程过程调用 (RPC) 服务器,否则除了提供支持 PHP 的页面(利用数据库连接)以外,Apache 服务器还很可能在执行其他操作。实际上,几乎可以确定的是,对于每个“页面”的请求(从浏览器中加载 index.php 网页),浏览器可能向 Web 服务器发出 10、20 甚至 30 个针对图像、Flash 文件或框架中其他嵌入文档(等等)的请求。根据服务器的配置,该事实可以反映有效使用持久连接的任何好处。由于对运行 PHP 的 Web 服务器进行微调以充分利用每个数据库连接请求至关重要,因此下面我们将了解相关的 Apache 1.3.x 配置设置:KeepAlive、MaxRequestsPerChild 和 MaxSpareServers。

查明 Apache 在哪些情况下在哪里中断数据库连接

现在,您已经了解了与 PHP 和持久连接相关的 Web 服务器中执行的操作,下面我们将了解如何提高性能。此处的基本目标是在 Apache 隐式关闭持久连接之前,最大限度地增加该连接的使用次数。

首先,了解一下 MaxRequestsPerChild 配置指令。这是一个处理持久连接时比较重要的指令,这是因为,顾名思义,它定义了在终止并重新启动进程之前单个 Apache 子进程可以服务的最大 HTTP 请求数。尽管理想情况下的设置为零(意味着每个子进程接受无限数量的连接),但如果 Web 服务器中运行的任何东西不稳定或发生内存泄露等情况,则必须使用该机制以使服务器保持最佳状态。因此,如果在 X 个请求之后中断子进程,则 X-1 是根据其利用持久性数据库连接的请求总数。这是您应找的第一个指令,因为它对于持久连接性能的影响最直接。

同样,MaxSpareServers 配置指令可通过终止实际上并未处理请求的非必要的 Apache 子进程“降低”在负载较低期间 Web 服务器上使用的资源数量。根据站点上遇到的负载模式类型,Apache(尽管它通常可以在一定程度上智能地管理该进程)也可能终止子进程,并进而过早地终止数据库连接。例如,对于经常遇到尖峰通信量的 Web 服务器而言,最好让更多的备用子进程运行,同时在高峰负载后长时间处于空闲状态的服务器可以终止它们来将资源释放给其他任务。

充分利用每个连接

现在,我们制定了两个由 Apache 终止其子进程的主要方法以及任何您可能已经建立的持久连接,下面我们将了解优化此行为的方法。尽管并不能总是简单地中断子进程(尤其是当子进程有可能随时间泄露内存时),但正确地配置 Apache 却具有重要意义。以下是几个可提高性能的方法。

 

  • 关闭 MaxRequestsPerChild。除非子进程泄露内存或随时间而中断,否则根本不需要对 Apache 子进程设置请求生命周期 - 默认值零(无限)是此时最佳的选择。如果确实必须将它设置为零以外的值,请尽可能灵活地对其进行设置以充分利用持久连接。
  • 灵活处理备用进程。几乎在每种情况下,都有一个或多个在专用计算机上运行的 Apache Web 服务器。因此,完全可以采用灵活的方法设置等待请求的“备用”服务器数。总之,您又不是按小时管理它们,并且使其处于活动状态使您以后可以避免昂贵的重新连接过程。
  • 使用 KeepAlive。无论出于何种原因您必须改良每个子进程的最大请求数,则您仍可以通过在 Apache 配置中启用 KeepAlive 指令更好地利用持久连接。“保持活动状态”意味着连接客户端可以打开一个指向单个子进程的连接并在关闭前请求多个文档。尽管它们均被称作“请求”(实际上向服务器发出了多个文档请求),但在涉及 MaxRequestsPerChild 配置指令的情况下,Apache 仅将其视为一个请求。因此,在强制重新连接(当子进程终止并且被另一个进程替换时)之前,KeepAlive 将帮助您尽可能长时间地保留持久连接。
  • 将 PHP 隔离在其自己的 Web 环境中。另一个提高整个站点以及 Oracle 数据库性能的技巧是将运行 PHP 脚本的 Web 服务器与提供静态内容的 Web 服务器分开。注意,Apache 不对 PHP 脚本请求与任何其他请求进行区分,因此如果执行 PHP 代码的服务器同时还提供静态图像,那么您甚至可以快速运行最灵活的 MaxRequestsPerChild 设置。但即使它位于同一台计算机上,也要将非 PHP 资源的提供转移到单独的 Apache Web 服务器并使用 mod_proxy 按照通常的方式透明地为其提供服务。您将不但提高总体性能(从现在开始,您可以真正简化静态服务器来优化静态文件的提供),而且还将使持久连接尽可能地达到最佳性能。

优化 OCI 扩展

Zend Core for Oracle(其中包括对 PHP 的 OCI 扩展重新整理的功能)提供了一些充分利用持久连接的新管理工具。这些工具表现为一组 PHP 配置指令,可用于 php.ini 文件中。

  • oci8.max_persistent。使用 oci8.max_persistent 配置指令,可以控制 PHP 每个进程与数据库服务器建立的持久连接数。在设计合理的应用程序中,不但不可能需要与同一服务器建立多个持久连接,而且此类操作将被视为一个错误。使用此设置,您可以帮助去掉这些不必要的数据库连接,从而提高总体性能。
  • oci8.persistent_timeout。使用 oci8.persistent_timeout 配置指令可以控制持久连接在自动关闭前连续空闲的时间。注意,Oracle 本身也可以通过用户配置文件或 Oracle Net 层控制此行为。
  • oci8.ping_interval。使用 oci8.ping_interval 配置指令可以控制 PHP 在检查现有持久连接的状态之前(在尝试使用它执行查询之前)必须等待的秒数。该间隔越长,所建立的连接就越有效 - 但请注意,获得此效率的代价是,当请求之间的连接中断时将更有可能提供一个性能较差的指向 PHP 应用程序的数据库连接。

结论

您现在已经了解了充分利用每个 Oracle 数据库连接所需的所有内容以及何时使用(或不使用)持久连接。尽管本文提供的信息特定于数据库连接,但仍可以使用本文描述的技巧优化任何遵循同一模式的持久资源。 John Coggeshall [ http://www.coggeshall.org/)] 是 Zend Technologies 的高级技术顾问,他为全球范围的客户提供专业服务。他从 1997 年开始接触 PHP,并在一些业界知名的出版社(如 Sams Publishing、Apress 和 O'Reilly)出版了三部有关 PHP 的书籍以及 100 多篇文章。John 还是 PHP 内核的积极贡献者(tidy 扩展的作者)、Zend 教育顾问委员会的成员,并经常在世界范围内的与 PHP 相关的会议上发表演讲。

将您的意见发送给我们