定義統一介面 Cache_Abstract,讓業務邏輯不依賴具體快取後端,支援運行時切換 Memcached / APC / File 三種驅動。
<?php
abstract class Cache_Abstract {
abstract public function fetch($key); // 讀取,失敗回傳 false
abstract public function store($key, $data, $ttl); // 寫入,TTL 秒數
abstract public function delete($key); // 刪除
}
?>
class Cache_APC extends Cache_Abstract {
public function fetch($key) {
return apc_fetch($key);
}
public function store($key, $data, $ttl) {
return apc_store($key, $data, $ttl);
}
public function delete($key) {
return apc_delete($key);
}
}
class Cache_MemCache extends Cache_Abstract {
public $connection;
public function __construct() {
$this->connection = new Memcache();
}
public function store($key, $data, $ttl) {
// flag=0, expire=$ttl
return $this->connection->set($key, $data, 0, $ttl);
}
public function fetch($key) {
return $this->connection->get($key);
}
public function delete($key) {
return $this->connection->delete($key);
}
public function addServer($host, $port = 11211, $weight = 10) {
// persistent=TRUE, weight=$weight
return $this->connection->addServer($host, $port, true, $weight);
}
}
使用範例:
$cache = new Cache_MemCache();
$cache->addServer('mem1.example.com');
$cache->addServer('mem2.example.com', 11211, 20); // 雙倍權重
$cache->addServer('mem3.example.com');
$cache->store('user:100', ['name' => 'John', 'email' => 'john@example.com'], 600);
$user = $cache->fetch('user:100');
class Cache_File extends Cache_Abstract {
public function store($key, $data, $ttl) {
$file = $this->getFileName($key);
$h = fopen($file, 'c+'); // 開啟讀寫,建立檔案
if (!$h) throw new Exception("無法開啟快取檔案: $file");
flock($h, LOCK_EX); // 獨佔鎖
ftruncate($h, 0); // 清空內容
$payload = serialize([time() + $ttl, $data]); // [過期時間戳, 資料]
if (fwrite($h, $payload) === false) {
fclose($h);
throw new Exception("寫入快取失敗: $file");
}
fclose($h);
}
public function fetch($key) {
$file = $this->getFileName($key);
if (!is_file($file)) return false;
$h = fopen($file, 'r');
if (!$h) return false;
flock($h, LOCK_SH); // 共享鎖
$payload = file_get_contents($file);
fclose($h);
$data = @unserialize($payload);
if (!$data || !is_array($data) || count($data) !== 2) {
@unlink($file);
return false;
}
// 檢查過期
if (time() > $data[0]) {
@unlink($file);
return false;
}
return $data[1];
}
public function delete($key) {
$file = $this->getFileName($key);
return is_file($file) ? unlink($file) : false;
}
private function getFileName($key) {
// 避免鍵名過長/特殊字元,採用 MD5 雜湊
return sys_get_temp_dir() . '/cache_' . md5($key);
}
}
使用範例:
$cache = new Cache_File();
$key = 'getUsers:all';
if (!$users = $cache->fetch($key)) {
// 模擬資料庫查詢
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->query('SELECT id, name, email FROM users');
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
$cache->store($key, $users, 600); // 快取 10 分鐘
}
foreach ($users as $u) {
echo $u['name'] . "\n";
}
| 功能 | 說明 |
|---|---|
| 多層快取 (L1/L2) | L1: APCu (本機記憶體、極快) → L2: Memcached (分散式)。讀取時先查 L1,未命中查 L2 並回填 L1。 |
| 標籤/群組失效 | 為快取項打標籤 (如 user:100 屬於 user、profile),提供 flushByTag($tag) 批次清除。 |
| 快取穿透防護 | fetch 未命中時,使用 getOrSet($key, $callable, $ttl) 原子操作,避免熱點 Key 同時回源。 |
| 序列化優化 | 改用 igbinary、msgpack 或 JSON,提升序列化速度與跨語言相容性。 |
| 監控指標 | 整合 Prometheus/StatsD:命中率、延遲、記憶體使用、驅逐數量。 |
get/set/delete/has 簡易介面,適合輕量專案。新專案建議直接採用符合 PSR 標準的成熟函式庫,避免重複造輪子。