PHP 快取分為頁面快取(輸出緩衝)、資料快取(變數/查詢結果)、記憶體快取。本文實作一個通用的檔案快取類別,並介紹 Memcached 記憶體快取整合方式。
<?php
/**
* 檔案快取類別
* 支援:頁面快取、變數快取、自動過期、目錄自動建立、快取清除
*/
class FileCache {
private $cache_dir;
private $default_ttl;
private $extension = '.cache.php';
public function __construct($cache_dir = './cache/', $default_ttl = 3600) {
$this->cache_dir = rtrim($cache_dir, '/') . '/';
$this->default_ttl = $default_ttl;
$this->ensureDir($this->cache_dir);
}
// 取得快取檔案路徑 (MD5 雜湊避免檔名過長/特殊字元)
private function getPath($key) {
return $this->cache_dir . md5($key) . $this->extension;
}
// 確保目錄存在
private function ensureDir($dir) {
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
}
// 寫入快取
public function set($key, $data, $ttl = null) {
$ttl = $ttl ?? $this->default_ttl;
$path = $this->getPath($key);
$content = "getPath($key);
if (!file_exists($path)) return false;
// 檢查過期 (讀取檔案前幾行判斷)
$meta = $this->readMeta($path);
if ($meta && $meta['expires'] < time()) {
$this->delete($key);
return false;
}
// 直接 include 回傳資料 (OPcache 可加速)
$data = @include $path;
return $data !== false ? $data : false;
}
// 讀取中繼資料 (不載入完整資料)
private function readMeta($path) {
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$meta = ['created' => 0, 'expires' => 0];
foreach ($lines as $line) {
if (strpos($line, 'Created:') === 0) {
$meta['created'] = (int)trim(str_replace('// Created:', '', $line));
} elseif (strpos($line, 'Expires:') === 0) {
$meta['expires'] = (int)trim(str_replace('// Expires:', '', $line));
}
}
return $meta;
}
// 刪除快取
public function delete($key) {
$path = $this->getPath($key);
if (file_exists($path)) {
return unlink($path);
}
return false;
}
// 清除所有快取
public function clear() {
$files = glob($this->cache_dir . '*' . $this->extension);
$count = 0;
foreach ($files as $file) {
if (is_file($file) && unlink($file)) $count++;
}
return $count;
}
// 取得快取資訊
public function info($key) {
$path = $this->getPath($key);
if (!file_exists($path)) return false;
$meta = $this->readMeta($path);
$meta['size'] = filesize($path);
$meta['path'] = $path;
$meta['is_expired'] = $meta['expires'] < time();
return $meta;
}
// 頁面快取輔助:開始捕捉輸出
public function startPageCache($key) {
$this->current_page_key = $key;
ob_start();
}
// 頁面快取輔助:結束並儲存
public function endPageCache($ttl = null) {
$content = ob_get_clean();
if ($content !== false) {
$this->set($this->current_page_key, $content, $ttl);
}
echo $content;
}
// 頁面快取輔助:檢查並輸出 (適用於簡單頁面)
public function checkPageCache($key) {
$cached = $this->get($key);
if ($cached !== false) {
echo $cached;
return true; // 已輸出快取
}
return false; // 需重新生成
}
}
?>
<?php
require 'FileCache.php';
$cache = new FileCache('./cache/', 1800); // 30 分鐘
// 檢查快取
$cache_key = 'user_list_' . $page;
$users = $cache->get($cache_key);
if ($users === false) {
// 快取未命中,查詢資料庫
$stmt = $pdo->prepare('SELECT * FROM users LIMIT ?, 20');
$stmt->execute([($page-1)*20]);
$users = $stmt->fetchAll();
// 寫入快取
$cache->set($cache_key, $users);
}
foreach ($users as $user) {
echo $user['name'] . '
';
}
?>
<?php
$cache = new FileCache('./cache/page/', 3600);
$page_key = 'page_' . md5($_SERVER['REQUEST_URI']);
// 檢查快取
if ($cache->checkPageCache($page_key)) {
exit; // 直接輸出快取內容結束
}
// 否則生成頁面...
?>
<html><body>
<h1>動態生成的頁面</h1>
<p>時間: <?= date('H:i:s') ?></p>
<?php
// 頁面結束前儲存快取
$cache->set($page_key, ob_get_contents());
?>
</body></html>
<?php
$cache = new FileCache('./cache/page/', 3600);
$page_key = 'page_' . md5($_SERVER['REQUEST_URI']);
// 檢查快取
if ($cache->checkPageCache($page_key)) {
exit;
}
// 開始捕捉
$cache->startPageCache($page_key);
?>
<html><body>
<h1>動態頁面</h1>
<p>時間: <?= date('H:i:s') ?></p>
<?php
// 結束並自動儲存
$cache->endPageCache();
?>
</body></html>
# 安裝 apt-get install memcached php-memcached # 或 pecl install memcached
<?php
/**
* Memcached 快取包裝類別
* 介面與 FileCache 相容,易於切換
*/
class MemCache {
private $memcached;
private $default_ttl;
public function __construct($host = '127.0.0.1', $port = 11211, $default_ttl = 3600) {
$this->memcached = new Memcached();
$this->memcached->addServer($host, $port);
$this->default_ttl = $default_ttl;
// 可選:設定序列化器 (igbinary 較快)
// $this->memcached->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_IGBINARY);
}
public function set($key, $data, $ttl = null) {
$ttl = $ttl ?? $this->default_ttl;
return $this->memcached->set($key, $data, $ttl);
}
public function get($key) {
return $this->memcached->get($key);
}
public function delete($key) {
return $this->memcached->delete($key);
}
public function clear() {
return $this->memcached->flush();
}
public function getMulti($keys) {
return $this->memcached->getMulti($keys);
}
public function setMulti($items, $ttl = null) {
$ttl = $ttl ?? $this->default_ttl;
return $this->memcached->setMulti($items, $ttl);
}
// 計數器 (原子操作)
public function increment($key, $offset = 1, $initial = 0, $expiry = 0) {
return $this->memcached->increment($key, $offset, $initial, $expiry);
}
public function decrement($key, $offset = 1, $initial = 0, $expiry = 0) {
return $this->memcached->decrement($key, $offset, $initial, $expiry);
}
}
?>
<?php
$cache = new MemCache('127.0.0.1', 11211, 1800);
// 快取資料庫查詢
$key = 'hot_articles';
$articles = $cache->get($key);
if ($articles === false) {
$articles = $pdo->query('SELECT * FROM articles WHERE views > 1000 ORDER BY views DESC LIMIT 10')->fetchAll();
$cache->set($key, $articles);
}
// 計數器:文章瀏覽數
$article_id = 123;
$views = $cache->increment("article_views_{$article_id}", 1, 0, 86400); // 24小時過期
echo "瀏覽數: $views";
// 批次操作
$keys = ['user_1', 'user_2', 'user_3'];
$users = $cache->getMulti($keys);
$missing = array_diff_key(array_flip($keys), $users);
if ($missing) {
// 查詢資料庫補齊...
$cache->setMulti($new_users);
}
?>
| 策略 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| 檔案快取 | 中小型專案、靜態頁面、查詢結果 | 零相依、OPcache 加速、持久化 | IO 瓶頸、分散式需共享儲存 |
| Memcached | 高併發、分散式、Session、計數器 | 記憶體級速度、原子操作、叢集 | 資料不持久、無備份、記憶體有限 |
| Redis | 複雜資料結構、發布訂閱、排行榜 | 豐富資料型別、持久化選項、Lua 腳本 | 單執行緒、記憶體限制 |
| APCu | 單機、Opcode 快取、小資料 | 極快、零網路延遲、共享記憶體 | 單機限制、重啟失效、無分散式 |
<?php
/**
* 多層快取:L1 (APCu/記憶體) -> L2 (Redis/Memcached) -> L3 (檔案/資料庫)
*/
class MultiLevelCache {
private $l1; // APCu
private $l2; // Redis/Memcached
private $l3; // FileCache
public function __construct() {
if (extension_loaded('apcu')) $this->l1 = new APCuCache();
$this->l2 = new MemCache(); // 或 Redis
$this->l3 = new FileCache();
}
public function get($key) {
// L1
if ($this->l1 && ($val = $this->l1->get($key)) !== false) return $val;
// L2
if (($val = $this->l2->get($key)) !== false) {
$this->l1 && $this->l1->set($key, $val, 60); // 回填 L1
return $val;
}
// L3
if (($val = $this->l3->get($key)) !== false) {
$this->l2->set($key, $val);
$this->l1 && $this->l1->set($key, $val, 60);
return $val;
}
return false;
}
public function set($key, $data, $ttl = 3600) {
$this->l1 && $this->l1->set($key, $data, min($ttl, 300));
$this->l2->set($key, $data, $ttl);
$this->l3->set($key, $data, $ttl);
}
public function delete($key) {
$this->l1 && $this->l1->delete($key);
$this->l2->delete($key);
$this->l3->delete($key);
}
}
?>
選擇快取策略取決於專案規模、架構與資料特性。中小專案檔案快取足夠;高併發需 Memcached/Redis;極致效能可疊加多層快取。