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

PHP 檔案快取類別實作:基於檔案修改時間的過期機制

     編輯:PHP 相關知識

前言:輕量級檔案快取的實用場景

在無法安裝 Redis/APCu 的虛擬主機環境,或需快速為特定頁面加速時,基於檔案修改時間 (filemtime) 的快取類別是極佳選擇。本文實作一個簡潔、可直接使用的 PageCache 類別,支援自動過期、直接輸出或回傳內容兩種模式。

PageCache 類別完整程式碼

<?php
class PageCache {
    /** @var string 快取檔案完整路徑 */
    public $file;

    /** @var int 快存有效秒數,預設 1 小時 */
    public $cacheTime = 3600;

    /**
     * 建構子
     * @param string $file     快取檔案路徑
     * @param int    $cacheTime 快取秒數
     */
    public function __construct($file, $cacheTime = 3600) {
        $this->file = $file;
        $this->cacheTime = $cacheTime;
    }

    /**
     * 取得快取內容
     * @param bool $output true=直接輸出並導向, false=回傳字串
     * @return mixed false|string
     */
    public function get($output = true) {
        // 檢查:檔案存在、未過期、未強制重新整理
        if (is_file($this->file) 
            && (time() - filemtime($this->file)) <= $this->cacheTime 
            && !isset($_GET['nocache'])) {
            
            if ($output) {
                // 直接導向快取檔案 (需為完整 HTML)
                header('Location: ' . $this->file);
                exit;
            } else {
                return file_get_contents($this->file);
            }
        }
        return false;
    }

    /**
     * 寫入快取內容
     * @param string $content HTML 字串
     */
    public function set($content) {
        $fp = fopen($this->file, 'wb');
        if ($fp) {
            fwrite($fp, $content);
            fclose($fp);
            // 可選:設定檔案權限
            @chmod($this->file, 0644);
        }
    }
}

使用範例:文章頁面快取

<?php
require 'PageCache.php';

// 1. 依據文章 ID 產生唯一快取檔名
$articleId = (int)($_GET['id'] ?? 0);
if (!$articleId) { header('HTTP/1.1 404 Not Found'); exit; }

$cacheFile = __DIR__ . '/cache/article_' . $articleId . '.html';
$cache = new PageCache($cacheFile, 3600); // 1小時

// 2. 嘗試讀取快取
if ($cache->get()) {
    // get() 內部已處理輸出並 exit,程式不會執行到這裡
}

// 3. 快取未命中:執行昂貴操作 (查詢 DB、渲染模板)
$article = $db->query("SELECT * FROM articles WHERE id = ?", [$articleId])->fetch();
if (!$article) { header('HTTP/1.1 404 Not Found'); exit; }

// 渲染 HTML (此處簡化,實際建議用模板引擎)
ob_start();
include 'article_template.php';
$html = ob_get_clean();

// 4. 寫入快取
$cache->set($html);

// 5. 輸出
echo $html;

進階用法:條件式快取與標籤清除

1. 管理後台更新文章時清除快取

<?php
// admin/update_article.php
$articleId = (int)$_POST['id'];
$db->update('articles', ['title'=>$_POST['title'], 'content'=>$_POST['content']], ['id'=>$articleId]);

// 刪除對應快取
$cacheFile = __DIR__ . '/../cache/article_' . $articleId . '.html';
if (file_exists($cacheFile)) unlink($cacheFile);

// 同步清除列表頁快取
$listCacheDir = __DIR__ . '/../cache/';
foreach (glob($listCacheDir . 'list_page_*.html') as $f) unlink($f);

echo "更新成功,快取已清除";

2. 支援標籤/群組清除 (擴充版)

class TaggedPageCache extends PageCache {
    private $tagFile;

    public function __construct($file, $cacheTime = 3600, $tag = '') {
        parent::__construct($file, $cacheTime);
        $this->tagFile = dirname($file) . '/.tags_' . md5($tag) . '.txt';
    }

    public function set($content) {
        parent::set($content);
        // 記錄此快取屬於哪個標籤
        file_put_contents($this->tagFile, $this->file . PHP_EOL, FILE_APPEND | LOCK_EX);
    }

    public static function clearByTag($cacheDir, $tag) {
        $tagFile = $cacheDir . '/.tags_' . md5($tag) . '.txt';
        if (file_exists($tagFile)) {
            $files = file($tagFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
            foreach ($files as $f) { if (file_exists($f)) @unlink($f); }
            @unlink($tagFile);
        }
    }
}

效能優化與注意事項

1. 避免快取擊穿 (Cache Stampede)

// 簡易鎖機制
$lockFile = $cacheFile . '.lock';
$fp = fopen($lockFile, 'w');
if (flock($fp, LOCK_EX | LOCK_NB)) {
    // 取得鎖,重建快取
    $html = renderPage();
    $cache->set($html);
    flock($fp, LOCK_UN);
} else {
    // 未取得鎖,等待並重試讀取
    sleep(1);
    echo file_get_contents($cacheFile);
}
fclose($fp);

2. 使用 tmpfs 掛載快取目錄

# /etc/fstab
tmpfs  /var/www/html/cache  tmpfs  defaults,size=100M,uid=www-data,gid=www-data  0 0

將快取目錄掛載在記憶體,獲得接近 Redis 的讀寫速度,且無需額外服務。

3. 快取檔案命名最佳實踐

  • 使用 md5() 或 sha1() 雜湊長參數:cache_' . md5($queryString) . '.html
  • 避免檔名過長超過檔案系統限制 (255 字元)
  • 分層目錄:cache/a/b/cache_abc123.html 避免單目錄檔案過多

與其他快取方案對比

方案速度持久性跨伺服器複雜度適用場景
PageCache (檔案)中 (SSD/tmpfs 快)永久需共享儲存極低單機、虛擬主機、靜態頁面
APCu極快重啟消失否低單機、熱點資料
Redis極快可持久化是中叢集、分散式、需 TTL/結構化
OPcache極快記憶體否零設定PHP 位元碼快取 (必開)

總結

PageCache 類別以不到 50 行程式碼實現了實用的頁面級快取,核心邏輯是 filemtime() 判斷過期。適用於流量中等、內容更新頻率可預測的場景。生產環境建議搭配 tmpfs、鎖機制、標籤清除等進階功能,或直接升級至 Redis 等專業快取中介軟體。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved