Wave Cache 是 PHP 生态中一个设计比较灵活的缓存抽象层,它最大的特点是将缓存 API 与底层存储解耦。也就是说,上层代码始终调用同一组方法,而底层可以在文件缓存、Redis、Memcached 等后端之间自由切换。这种设计对于需要实现页面片段缓存的场景非常友好:页面中那些生成成本高但变化不频繁的区块,比如首页推荐位、分类导航、热门榜单,都可以单独缓存起来,请求到来时直接读取缓存好的 HTML 片段,跳过数据库查询和模板渲染,整体响应速度可以获得数量级的提升。本文将从安装配置、驱动集成、片段缓存的实现方式以及后端选择策略几个方面详细讲解。

一、Wave Cache 的安装与基本配置
Wave Cache 可以通过 Composer 安装。如果项目已经使用 Composer 管理依赖,只需在项目根目录执行一条命令即可完成安装。安装完成后,它会自动注册自动加载机制,无需手动 include 任何文件。
composer require wave/cache
安装完成后,第一步要做的是初始化缓存管理器并指定默认后端。Wave Cache 采用驱动模式(Driver Pattern),每个后端对应一个驱动类。下面这段代码演示了如何初始化一个基于文件的缓存实例,并设置缓存目录与默认过期时间。
<?php
require 'vendor/autoload.php';
use Wave\Cache\CacheManager;
$manager = new CacheManager();
// 注册文件后端驱动
$manager->extend('file', function () {
return new \Wave\Cache\Drivers\FileDriver([
'path' => __DIR__ . '/runtime/cache',
// 缓存文件目录权限
'dirMode' => 0755,
]);
});
// 设置默认驱动
$manager->setDefaultDriver('file');
$cache = $manager->driver();
上面的代码中,extend 方法用于注册一个自定义驱动工厂,闭包返回具体的驱动实例。setDefaultDriver 指定默认使用哪个后端,之后通过 driver() 方法就能拿到缓存操作对象。文件后端的优势是不依赖外部服务,部署简单,适合小型站点或者本地开发环境;缺点是高并发下磁盘 IO 容易成为瓶颈,而且不支持分布式的原子操作。
二、集成 Redis 与 Memcached 后端
生产环境中更常见的做法是使用内存型后端。Wave Cache 同样内置了 Redis 和 Memcached 的驱动支持,注册方式与文件驱动类似,只需要传入对应的连接参数。
<?php
use Wave\Cache\CacheManager;
$manager = new CacheManager();
// 注册 Redis 后端
$manager->extend('redis', function () {
return new \Wave\Cache\Drivers\RedisDriver([
'host' => '127.0.0.1',
'port' => 6379,
'password' => null,
'database' => 0,
'prefix' => 'wave:',
]);
});
// 注册 Memcached 后端
$manager->extend('memcached', function () {
return new \Wave\Cache\Drivers\MemcachedDriver([
['host' => '127.0.0.1', 'port' => 11211, 'weight' => 100],
]);
});
$redisCache = $manager->driver('redis');
$memCache = $manager->driver('memcached');
这段代码注册了两个后端,通过 driver('redis') 和 driver('memcached') 分别获取对应的缓存实例。两个后端各有侧重:Redis 支持丰富的数据结构和持久化,单线程模型保证了命令的原子性,适合对一致性要求较高的场景;Memcached 采用多线程架构,纯内存且逻辑简单,在纯粹的键值读写上吞吐量往往更高,但不支持持久化,重启后数据会全部丢失。
还可以利用前缀(prefix)来隔离不同业务模块的缓存键。例如给页面片段统一加上 frag: 前缀,这样在批量清理时可以直接按前缀删除,不会误伤其他数据。需要注意的是,Redis 的 KEYS 命令在大库中会阻塞服务,建议使用 SCAN 命令进行迭代式的模糊删除,Wave Cache 的 Redis 驱动内部已经做了这方面的处理。
三、实现页面片段缓存的核心代码
所谓片段缓存,就是把页面中某个区块的最终 HTML 输出缓存起来。实现的关键在于输出缓冲(Output Buffering):先开启缓冲区,执行区块的渲染逻辑,然后捕获缓冲区内容写入缓存,下次请求直接取出这段 HTML 输出即可。Wave Cache 提供了 remember 方法配合回调来简化这一过程。
<?php
// $cache 为之前初始化的缓存实例
function renderHotList($cache, $categoryId)
{
// 缓存键中带上分类 ID,保证不同分类互不干扰
$key = 'frag:hotlist:' . $categoryId;
// remember 方法:缓存不存在时执行回调,并把返回值写入缓存
return $cache->remember($key, 600, function () use ($categoryId) {
// 以下是需要被缓存的高成本逻辑
$items = queryHotItems($categoryId);
$html = '<div class="hot-list">';
foreach ($items as $item) {
$html .= '<li>' . htmlspecialchars($item['title']) . '</li>';
}
$html .= '</div>';
return $html;
});
}
// 在页面模板中直接输出
echo renderHotList($cache, 5);
remember 方法的第三个参数是一个回调,只有缓存未命中时才会执行,执行结果会自动写入缓存并返回。600 表示缓存有效期,单位为秒。这样一来,即便区块内部包含几十条 SQL 查询,也只会在缓存过期后的第一次请求中执行,其余请求全部命中缓存。
如果要缓存的片段中包含大量原生模板代码,不方便封装成函数返回字符串,可以借助输出缓冲函数 ob_start 来捕获。Wave Cache 也提供了更语义化的 section 用法,示例如下。
<?php
$key = 'frag:sidebar';
if ($cache->has($key)) {
// 直接输出缓存内容
echo $cache->get($key);
} else {
ob_start();
?>
<aside class="sidebar">
<h3>热门标签</h3>
<ul>
<?php foreach (getHotTags() as $tag): ?>
<li><?php echo htmlspecialchars($tag); ?></li>
<?php endforeach; ?>
</ul>
</aside>
<?php
$content = ob_get_clean();
$cache->set($key, $content, 1800);
echo $content;
}
这种写法的好处是可以直接在 PHP 与 HTML 混排的模板文件中使用,不需要把模板逻辑搬进闭包里。判断逻辑也很直观:has 检查缓存是否存在,命中则 get 输出,未命中则渲染后 set 写回。三个方法组合起来构成了片段缓存最经典的读写流程。
四、缓存失效策略与后端选择建议
缓存用得不好反而会带来数据不一致的问题,因此失效策略必须提前设计。最常见的做法是设置合理的 TTL,让缓存自然过期,适合对实时性要求不高的内容。对于必须及时更新的数据,可以在数据变更的代码路径中主动删除对应片段缓存。
<?php
// 后台更新了分类为 5 的推荐数据后,主动清理对应片段
function refreshHotList($cache, $categoryId)
{
$cache->forget('frag:hotlist:' . $categoryId);
}
// 按前缀批量清理(依赖后端支持,Redis 后端基于 SCAN 实现)
function flushFragments($cache)
{
$cache->flushPrefix('frag:');
}
关于后端的选择,可以参考以下对比。单机小流量站点用文件后端即可,省去运维成本;并发较高的 Web 应用优先选 Redis,因为它既能做缓存又能承担计数、队列等职能,一套组件多种用途;纯粹的读多写少、追求极限吞吐的场景可以考虑 Memcached。还有一种常见的组合方案是把 Redis 作为默认后端,同时注册文件后端作为降级备用,当 Redis 连接异常时自动切换,保证页面不会因为缓存服务故障而彻底瘫痪。
最后要注意缓存粒度的把控。片段缓存切得太细,键的管理成本和缓存 miss 的次数都会上升;切得太粗,又会导致任何一点数据变化都让整个大区块失效。实践经验是按照页面中数据更新节奏来划分区块:更新频繁的部分不缓存或用短 TTL,稳定部分用长 TTL 并配合主动失效,这样才能在性能与一致性之间找到平衡点。
Wave Cache页面片段缓存PHP缓存修改时间:2026-09-05 15:51:00