PHP 缓存
PHP 本身的运行速度已经相当快了,但当你的应用程序需要建立远程连接、频繁加载大量文件或处理复杂计算时,性能瓶颈依然会显现。
值得庆幸的是,目前有许多成熟的工具可以用来加速应用程序的特定部分,或者减少那些耗时任务被重复执行的次数。这就是缓存 (Caching) 发挥作用的地方。
1. Opcode 缓存
当一个 PHP 文件被执行时,它首先需要被编译成 opcodes(操作码),即 CPU 能够直接理解的机器语言指令。如果源代码没有发生任何改变,那么编译出来的 opcodes 也会是一模一样的。因此,在每次请求时都重复这个编译步骤,完全是对 CPU 资源的严重浪费。
Opcode 缓存通过将编译好的 opcodes 直接存储在内存中,并在随后的调用中直接复用它们,从而完美地防止了冗余编译。通常,它会首先检查文件的签名或最后修改时间,以确认代码是否发生了更改,进而决定是否需要重新编译。
启用 Opcode 缓存通常会为你的应用程序带来非常显著的速度提升。
从 PHP 5.5 开始,PHP 官方已经内置了一个极其优秀的 Opcode 缓存组件——Zend OPcache。根据你使用的 PHP 安装包或 Linux 发行版不同,它通常是默认开启的。你可以检查 php.ini 中的 opcache.enable 设置,或者直接查看 phpinfo() 的输出信息来确认它是否已激活。对于更早的 PHP 版本,可以通过 PECL 扩展库来安装它。
延伸阅读:关于 Opcode 缓存
- Zend OPcache (自 PHP 5.5 起随官方捆绑发布)
- Zend OPcache 现在是开源的 (前身为 Zend Optimizer+)
- WinCache (针对微软 Windows Server 环境的扩展)
- 维基百科上的 PHP 加速器列表
- PHP 预加载 (Preloading)- 适用于 PHP >= 7.4
2. 对象缓存 (Object Caching)
有些时候,在代码中缓存单个对象 (Objects) 或特定数据是非常有益的。比如,那些需要消耗大量计算资源才能获取的数据,或者那些结果极少发生改变的数据库查询结果。
你可以使用对象缓存软件将这些零散的数据片段直接保存在内存中,以便稍后以极快的速度进行访问。如果你在初次获取这些数据后就将它们保存到缓存系统里,那么在接下来的请求中直接从缓存提取,不仅能获得巨大的性能飞跃,还能极大程度地减轻数据库服务器的负载压力。
许多流行的字节码缓存解决方案同时也允许你缓存自定义的业务数据,这让你有了更充分的理由去利用它们。例如,APCu 和 WinCache 都提供了易于使用的 API,让你能在 PHP 代码中将数据直接保存到它们的内存缓存里。
目前最常用的内存对象缓存系统是 APCu 和 Memcached。
2.1 APCu vs Memcached 选型对比
- APCu: 它是对象缓存的极佳选择。它内置了一套极其简单的 API 用于向内存中添加自定义数据,并且安装和使用都极其简便。APCu 唯一真正的局限性在于:它与安装它的那台服务器(以及 PHP 进程)死死绑定在一起。
- Memcached: 它是作为一个独立的服务安装的,并且可以通过网络进行访问。这意味着你可以将对象存储在一个集中的、超高速的数据中心里,然后让几十上百台不同的应用服务器共同从中拉取数据。
2.2 必须警惕的 APCu 进程共享限制
请特别注意:APCu 的缓存是否在多个 PHP 进程之间共享,完全取决于你如何运行 PHP:
- PHP-FPM 模式: 缓存在所有资源池 (pools) 的所有进程之间完全共享。这是最理想的 Web 运行状态。
- (Fast-)CGI 模式 (嵌入 Web 服务器): 缓存不共享。即每个独立的 PHP 进程都会拥有自己完全隔离的一份 APCu 数据,这会导致内存浪费和命中率低下。
- CLI (命令行) 模式: 缓存不共享,并且缓存只在当前这条命令执行的生命周期内存在,命令跑完缓存就清空了。
因此,你必须清楚自己的运行环境和目标。如果你的架构比较复杂,你可能需要考虑使用 Memcached(或 Redis),因为它们独立于 PHP 进程之外,不会受到这些限制的困扰。
在单台服务器的网络配置中,APCu 在访问速度上通常会击败 Memcached(因为去除了网络通信开销)。但是,Memcached 在向外横向扩展(Scale up)时会更快、走得更远。如果你预计你的应用不会在多台服务器上做分布式运行,或者你不需要 Memcached 提供的额外高级特性,那么 APCu 极大概率是你做对象缓存的最佳选择。
2.3 APCu 对象缓存代码示例
下面是一段使用 APCu 实现标准缓存逻辑的示例:
<?php
// 检查缓存中是否已经存在键名为 'expensive_data' (高昂成本数据) 的数据
$data = apcu_fetch('expensive_data');
// 如果返回 false,说明缓存未命中(数据不存在或已过期)
if ($data === false) {
// 数据不在缓存中;执行极其耗时的操作来获取数据
// 并将结果保存到缓存中,以便后续直接使用
apcu_add('expensive_data', $data = get_expensive_data());
}
// 打印最终的数据
print_r($data);延伸阅读:了解流行的对象缓存系统