首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 檔案快取類完整比較:三種實作架構與效能優化指南

     編輯:PHP 綜合知識

檔案快取是中小型專案、無 Redis/Memcached 環境的首選。本文深度剖析三種主流實作:**魔術方法封裝**、**Key-Value 介面**、**單例模式進階版**,並補充鎖機制、序列化選擇、GC 清理策略,助你選出最適合的方案。

方案一:魔術方法封裝(直覺好用)

<?php
class FileCache {
    public $ttl = 3600;
    public $dir = __DIR__ . '/cache';
    public function __construct() {
        is_dir($this->dir) || mkdir($this->dir, 0755, true);
    }
    public function __set(string $key, mixed $val): void {
        $this->set($key, $val);
    }
    public function __get(string $key): mixed {
        return $this->get($key);
    }
    public function __isset(string $key): bool {
        return $this->has($key);
    }
    public function __unset(string $key): void {
        $this->delete($key);
    }
    protected function path(string $key): string {
        return $this->dir . '/' . md5($key) . '.cache';
    }
    public function set(string $key, mixed $val, int $ttl = null): bool {
        $data = ['v' => $val, 'e' => time() + ($ttl ?? $this->ttl)];
        return file_put_contents($this->path($key), serialize($data), LOCK_EX) !== false;
    }
    public function get(string $key, mixed $default = null): mixed {
        $file = $this->path($key);
        if (!is_file($file)) return $default;
        $data = @unserialize(file_get_contents($file));
        if (!$data || time() > $data['e']) {
            unlink($file);
            return $default;
        }
        return $data['v'];
    }
    public function has(string $key): bool {
        return $this->get($key, '__NOT_FOUND__') !== '__NOT_FOUND__';
    }
    public function delete(string $key): bool {
        return @unlink($this->path($key));
    }
    public function clear(): void {
        array_map('unlink', glob($this->dir . '/*.cache'));
    }
}

// 使用
$cache = new FileCache();
$cache->user_1 = ['name' => 'Alice', 'vip' => true]; // __set
echo $cache->user_1['name']; // __get
unset($cache->user_1); // __unset
?>

✅ 語法糖豐富、適合簡單場景。⚠️ 魔術方法效能略低、難以型別提示。

方案二:標準 Key-Value 介面(框架風格,CI/Laravel 風格)

<?php
class FileStore {
    private string $path;
    private int $defaultTtl;
    public function __construct(string $path, int $defaultTtl = 3600) {
        $this->path = rtrim($path, '/') . '/';
        $this->defaultTtl = $defaultTtl;
        is_dir($this->path) || mkdir($this->path, 0755, true);
    }
    private function file(string $key): string {
        return $this->path . 'cache_' . md5($key) . '.php';
    }
    public function set(string $key, mixed $value, int $ttl = null): bool {
        $ttl ??= $this->defaultTtl;
        $content = "file($key), $content, LOCK_EX) !== false;
    }
    public function get(string $key, mixed $default = null): mixed {
        $file = $this->file($key);
        if (!is_file($file)) return $default;
        $expire = (int)preg_replace('/^.*?EXPIRE:\s*(\d+).*$/s', '$1', file_get_contents($file));
        if (time() > $expire) {
            unlink($file);
            return $default;
        }
        return include $file; // 直接執行 PHP 回傳值,極快
    }
    public function delete(string $key): bool { return @unlink($this->file($key)); }
    public function flush(): void { array_map('unlink', glob($this->path . 'cache_*.php')); }
}
?>

✅ 使用 `var_export` + `include` 避免 `unserialize` 風險、速度極快、程式碼可讀。⚠️ 僅限 PHP 陣列/標量/物件(無資源)。

方案三:單例進階版(支援標籤、群組清理、檔案鎖)

<?php
class Cache {
    private static ?self $instance = null;
    private string $dir;
    private int $ttl;
    private bool $locking;
    private function __construct(string $dir, int $ttl = 3600, bool $locking = true) {
        $this->dir = rtrim($dir, '/') . '/';
        $this->ttl = $ttl;
        $this->locking = $locking;
        is_dir($this->dir) || mkdir($this->dir, 0755, true);
    }
    public static function init(string $dir, int $ttl = 3600, bool $locking = true): self {
        return self::$instance ??= new self($dir, $ttl, $locking);
    }
    public static function getInstance(): self {
        return self::$instance ?? throw new LogicException('Cache not initialized');
    }
    private function file(string $key): string {
        return $this->dir . 'c_' . md5($key) . '.dat';
    }
    public function set(string $key, mixed $value, int $ttl = null, array $tags = []): bool {
        $data = [
            'v' => $value,
            'e' => time() + ($ttl ?? $this->ttl),
            't' => $tags, // 標籤
            'm' => time(),
        ];
        $fp = fopen($this->file($key), 'cb');
        if (!$fp) return false;
        if ($this->locking) flock($fp, LOCK_EX);
        ftruncate($fp, 0);
        fwrite($fp, serialize($data));
        if ($this->locking) flock($fp, LOCK_UN);
        fclose($fp);
        // 寫入標籤索引
        foreach ($tags as $tag) {
            $this->addTagIndex($tag, $key);
        }
        return true;
    }
    private function addTagIndex(string $tag, string $key): void {
        $idxFile = $this->dir . 'tag_' . md5($tag) . '.idx';
        $keys = is_file($idxFile) ? unserialize(file_get_contents($idxFile)) : [];
        $keys[$key] = true;
        file_put_contents($idxFile, serialize($keys), LOCK_EX);
    }
    public function get(string $key, mixed $default = null): mixed {
        $file = $this->file($key);
        if (!is_file($file)) return $default;
        $fp = fopen($file, 'rb');
        if (!$fp) return $default;
        if ($this->locking) flock($fp, LOCK_SH);
        $data = unserialize(stream_get_contents($fp));
        if ($this->locking) flock($fp, LOCK_UN);
        fclose($fp);
        if (!$data || time() > $data['e']) {
            $this->delete($key);
            return $default;
        }
        return $data['v'];
    }
    public function delete(string $key): bool {
        // 從標籤索引移除
        $file = $this->file($key);
        if (is_file($file)) {
            $data = @unserialize(file_get_contents($file));
            if ($data && !empty($data['t'])) {
                foreach ($data['t'] as $tag) $this->removeTagIndex($tag, $key);
            }
        }
        return @unlink($file);
    }
    public function clearByTag(string $tag): int {
        $idxFile = $this->dir . 'tag_' . md5($tag) . '.idx';
        if (!is_file($idxFile)) return 0;
        $keys = unserialize(file_get_contents($idxFile));
        $count = 0;
        foreach (array_keys($keys) as $key) {
            if ($this->delete($key)) $count++;
        }
        unlink($idxFile);
        return $count;
    }
    public function flush(): void {
        array_map('unlink', glob($this->dir . '*'));
    }
}

// 使用
Cache::init('/tmp/my_cache', 7200);
Cache::getInstance()->set('heavy_query', $rows, 3600, ['reports', 'dashboard']);
$rows = Cache::getInstance()->get('heavy_query');
Cache::getInstance()->clearByTag('reports'); // 一次清理相關快取
?>

序列化格式選擇對比

格式速度體積安全性適用場景
serialize/unserialize中中⚠️ 物件注入風險需儲存物件、資源
var_export + include**快**大✅ 純 PHP 程式碼陣列/標量、高讀取頻率
json_encode/decode中小✅ 安全跨語言、API 快取
igbinary (擴充套件)**最快****最小**✅ 安全高效能、大物件

效能優化關鍵點

  1. 檔案鎖:`LOCK_EX` 寫入、`LOCK_SH` 讀取,防止併發損壞。
  2. 目錄分片**:單目錄超過 1 萬檔案 `readdir` 變慢,用 `md5($key)[0].md5($key)[1]/` 兩級分片。
  3. 定期 GC**:`cron` 執行 `find /cache -type f -mmin +60 -delete`,避免殭屍檔案堆積。
  4. 記憶體映射**:極高頻小快取考慮 `mmap` 或直接用 `APCu`/`OPcache`。

選型建議

  • **簡單專案、快速開發** → 方案一(魔術方法)
  • **框架整合、高讀取、安全優先** → 方案二(var_export + include)
  • **複雜快取邏輯、標籤清理、多伺服器同步** → 方案三或直接上 Redis
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved