在無 Redis/Memcached 的環境下,文件緩存是提升頁面響應速度的經典方案。本文解析一個集「數據查詢、模板渲染、序列化存儲、過期驗證」於一體的緩存函數,適合中小型項目快速接入。
function __cache($cachefile, $pertime, $sql, $templatefile) {
// $cachefile: 緩存文件絕對路徑
// $pertime: 緩存有效期(秒)
// $sql: 查詢 SQL 語句
// $templatefile: 模板文件路徑(不含 .php 後綴)
}
function __cache($cachefile, $pertime, $sql, $templatefile) {
global $db; // 依賴全局 $db 對象,需具備 query()、fetch() 方法
// 判斷緩存是否過期:當前時間 - 文件修改時間 >= 有效期
if (time() - @filemtime($cachefile) >= $pertime) {
// --- 緩存過期或不存在:重新構建 ---
$query = $db->query($sql);
$cachelist = [];
while ($r = $db->fetch($query)) {
$cachelist[] = $r;
}
// 包含模板文件,模板中直接使用 $cachelist 變量輸出 HTML
// 模板輸出會被緩存捕獲(需配合 ob_start 或模板內直接輸出)
include $templatefile . '.php';
// 將渲染後的數據序列化存儲
// 注意:此處序列化的是 $cachelist 數組,而非模板輸出的 HTML
$cacheserialize = serialize($cachelist);
file_put_contents($cachefile, $cacheserialize);
} else {
// --- 緩存有效:直接讀取反序列化 ---
$cachelist = unserialize(file_get_contents($cachefile));
include $templatefile . '.php';
}
}
請求到達
│
▼
檢查緩存文件修改時間
│
├─ 過期/不存在 ──▶ 執行 SQL 查詢 ──▶ 獲取數據數組
│ │
│ ▼
│ include 模板文件(渲染 HTML)
│ │
│ ▼
│ serialize 數據數組
│ │
│ ▼
│ file_put_contents 寫入緩存文件
│
└─ 未過期 ─────────▶ file_get_contents 讀取
│
▼
unserialize 還原數據
│
▼
include 模板文件(渲染 HTML)
代碼中 include $templatefile 直接輸出到瀏覽器,但緩存存儲的是 $cachelist 而非渲染後的 HTML。這意味著:
修正方案:使用輸出緩衝捕獲 HTML:
if (time() - @filemtime($cachefile) >= $pertime) {
// ... 查詢數據 ...
ob_start();
include $templatefile . '.php';
$html = ob_get_clean();
file_put_contents($cachefile, $html); // 直接存 HTML
} else {
echo file_get_contents($cachefile); // 直接輸出 HTML
}
建議傳入 PDO/mysqli 實例或使用依賴注入容器。
高並發下多進程可能同時重建緩存(「緩存擊穿」)。建議加文件鎖:
$fp = fopen($cachefile . '.lock', 'w');
flock($fp, LOCK_EX);
// ... 重建邏輯 ...
flock($fp, LOCK_UN);
fclose($fp);
需定時任務清理過期文件,避免磁盤佔滿。
| 適用 | 不適用 |
|---|---|
| 中小流量項目,無緩存中間件 | 高並發、分佈式部署(文件鎖不可跨機器) |
| 數據更新頻率低、讀多寫少 | 實時性要求極高的數據 |
| 快速原型、內部工具 | 大數據量(文件 IO 成為瓶頸) |
這個函數展示了文件緩存的核心邏輯,理解其原理與缺陷,有助於在選型時做出正確決策:能用 Redis 就別用文件,必須用文件時請注意並發與輸出捕獲。