隨著網站資料量增長,每次請求都查詢資料庫會造成效能瓶頸。快取將熱門資料儲存在存取速度較快的介質(記憶體、檔案、Redis),下次請求直接讀取快取,大幅降低回應延遲與資料庫負載。
| 層級 | 介質 | 速度 | 容量 | 持久性 | 典型用途 |
|---|---|---|---|---|---|
| Opcode | 記憶體 | 極快 | 小 | 進程存活期 | PHP 編譯後 bytecode (OPcache) |
| 資料快取 | 記憶體 | 極快 | 中 | TTL 過期 | Redis、Memcached、APCu |
| 頁面片段 | 記憶體/檔案 | 快 | 中 | TTL 過期 | Smarty block cache、Laravel view cache |
| 靜態檔案 | 磁碟 | 中 | 大 | 手動/自動清除 | 生成 HTML、JSON 靜態檔 |
| HTTP 快取 | 瀏覽器/CDN | 最快(無請求) | 大 | Cache-Control | 靜態資源、API 回應 |
<?php
ob_start(); // 開啟輸出緩衝
echo "頁面內容...";
$content = ob_get_contents(); // 取得緩衝內容
ob_end_clean(); // 清除緩衝並關閉
// 或直接輸出並關閉
ob_end_flush();
// 靜態頁面生成範例
ob_start();
include 'template.php';
$html = ob_get_clean();
file_put_contents('static/page.html', $html);
?>
<?php
class FileCache {
private string $cacheDir;
private int $defaultTtl = 180; // 秒
public function __construct(string $cacheDir) {
if (!is_dir($cacheDir)) {
if (!mkdir($cacheDir, 0755, true) && !is_dir($cacheDir)) {
throw new RuntimeException("無法建立快取目錄:$cacheDir");
}
}
$this->cacheDir = rtrim($cacheDir, '/');
}
public function set(string $key, $data, int $ttl = 0): bool {
$ttl = $ttl ?: $this->defaultTtl;
$file = $this->cacheDir . '/' . $this->sanitizeKey($key);
$payload = serialize([
'data' => $data,
'expire' => time() + $ttl,
]);
// 原子寫入:寫入暫存檔再 rename,避免並發讀到半寫入資料
$tmpFile = $file . '.tmp.' . random_bytes(8);
if (file_put_contents($tmpFile, $payload) === false) return false;
if (!rename($tmpFile, $file)) {
@unlink($tmpFile);
return false;
}
return true;
}
public function get(string $key, $default = null) {
$file = $this->cacheDir . '/' . $this->sanitizeKey($key);
if (!is_file($file)) return $default;
$payload = @unserialize(file_get_contents($file));
if (!$payload || !isset($payload['expire'], $payload['data'])) {
@unlink($file); // 損壞檔案刪除
return $default;
}
if (time() > $payload['expire']) {
@unlink($file); // 過期刪除
return $default;
}
return $payload['data'];
}
public function delete(string $key): bool {
$file = $this->cacheDir . '/' . $this->sanitizeKey($key);
return is_file($file) && unlink($file);
}
public function clear(): int {
$count = 0;
foreach (glob($this->cacheDir . '/*') as $file) {
if (is_file($file) && unlink($file)) $count++;
}
return $count;
}
private function sanitizeKey(string $key): string {
// 僅允許英數字、底線、連字號、點,防止路徑遍歷
return preg_replace('/[^a-zA-Z0-9_\-\.]/', '_', $key);
}
}
// 使用範例
$cache = new FileCache(__DIR__ . '/cache');
// 寫入
$cache->set('user:1001', ['name' => 'John', 'role' => 'admin'], 300);
// 讀取
$user = $cache->get('user:1001');
if ($user === null) {
// 快取未命中:查詢資料庫
$user = $db->query("SELECT * FROM users WHERE id = 1001")->fetch();
$cache->set('user:1001', $user, 300);
}
?>
Cache::、Symfony CacheInterface、Yii yii\caching\Cache 統一介面,切換後端零修改業務碼。| 模式 | 適用場景 | 關鍵點 |
|---|---|---|
| Cache-Aside (Lazy Loading) | 讀多寫少、容許短暫不一致 | 應用層負責讀寫快取,失效由應用決定 |
| Write-Through | 資料一致性要求高 | 寫入時同步更新快取與資料庫 |
| Write-Behind | 寫入極頻繁、可容忍延遲持久化 | 非同步批次寫入資料庫,需防資料遺失 |
| Refresh-Ahead | 可預測的熱點資料 | 背景預先重建快取,避免冷啟動延遲 |
快取是高效能 PHP 架構的核心組件。理解各層級快取特性、掌握 Cache-Aside 基本模式、選擇合適的後端(Redis/APCu),並建立完善的快取失效、監控、預熱機制,才能在流量增長時保持系統穩定回應。