红穆笔记
首頁 踩坑總結 高性能 PHP 緩存系統設計:智能清理與計數管理
踩坑總結

高性能 PHP 緩存系統設計:智能清理與計數管理

高性能 PHP 缓存系统设计:智能清理与计数管理

 Web 項目中,頁面緩存是提升性能的常用手段。我原來使用的是一個簡單的文件緩存插件【帝國cms PHP緩存網頁代碼 加速訪問,它把所有頁面一視同仁地緩存下來。但實際業務中,有些頁面(如首頁)每天訪問量很大,而另一些頁面(如冷門文章)可能只有偶爾幾次訪問。

這種“一刀切”的緩存策略導致大量低價值緩存文件堆積,佔用了磁盤空間和 inode 資源。我需要的是一套智能緩存清理機制:能夠根據頁面訪問熱度決定保留還是刪除,並且保證緩存總數不超過預設上限。

二、整體設計思路

2.1 緩存存儲

  • 使用文件緩存,利用 PHP 的 ob_start 捕捉輸出,壓縮後存儲。

  • 文件按 URL 的 MD5 值分散存儲,避免單個目錄文件過多。

  • 目錄結構:cacheFile/{md5最後1位}/{md5最後2位}/{md5}.php,共 16 + 256 個目錄。

2.2 訪問計數

  • 每次命中緩存時,在 MySQL 中記錄該頁面的訪問次數。

  • 計數週期爲 7 天,超過 7 天且訪問次數低於閾值(如 20 次)的緩存將被清理。

2.3 清理策略(三階段)

  1. 階段一:刪除超過保留天數且訪問次數不足的緩存(直接操作數據庫和文件)。

  2. 階段二:若總緩存數超過上限(如 100 萬),按緩存時間從舊到新刪除,直到數量達標。

  3. 階段三:掃描文件系統,修復數據庫與文件系統的不一致(新增缺失記錄,刪除多餘記錄),並控制掃描速度以免影響服務器。

三、數據庫設計

只需一張表,字段極少:

CREATE TABLE cache_stat (
    url_md5 CHAR(32) NOT NULL COMMENT 'MD5值',
    access_count INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '訪問次數',
    cache_time INT UNSIGNED NOT NULL COMMENT '文件修改時間戳',
    PRIMARY KEY (url_md5),
    INDEX idx_cache_time (cache_time),
    INDEX idx_access_count (access_count)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  • cache_time 存儲文件的 mtime,用於判斷超時。

  • 索引加速階段一和階段二的查詢。

四、核心代碼實現

4.1 緩存主文件 cache.php

該文件放在項目入口(如帝國 CMS 的 e/ 目錄),負責攔截輸出、生成緩存和更新計數。

<?php
/******************
 * 緩存主邏輯(新目錄結構)
 * 所有文件統一放在 cacheFile/ 下,按 md5 後 1/2 位分兩層
 ******************/
if (1 == 1) {

    // ========== 可配置參數 ==========
    define('CACHE_ROOT', dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . 'cacheFile');
    define('CACHE_FIX', '.php');
    define('CACHE_COMPRESS_LEVEL', 6);

    // 數據庫配置(請按實際情況修改)
    define('DB_HOST', 'localhost');
    define('DB_NAME', 'cache_music');
    define('DB_USER', 'cache_music');
    define('DB_PASS', 'eb7rydwKyaxRW7Gs');

    date_default_timezone_set("Asia/Shanghai");

    // 創建緩存根目錄
    if (!file_exists(CACHE_ROOT)) {
        mkdir(CACHE_ROOT, 0755, true);
        chmod(CACHE_ROOT, 0755);
    }

    // ========== 數據庫操作函數 ==========
    /**
     * 獲取 PDO 連接(單例)
     */
    function getDb() {
        static $pdo = null;
        if ($pdo === null) {
            try {
                $pdo = new PDO(
                    "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
                    DB_USER,
                    DB_PASS
                );
                $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            } catch (PDOException $e) {
                error_log("DB connect failed: " . $e->getMessage());
                return null;
            }
        }
        return $pdo;
    }

    /**
     * 增加訪問計數(命中緩存時調用)
     * 增加 $mtime 參數,確保記錄缺失時能存入正確的文件修改時間
     */
    function incrementAccessCount($md5, $mtime) {
        $pdo = getDb();
        if (!$pdo) return;
        $sql = "INSERT INTO cache_stat (url_md5, access_count, cache_time) 
                VALUES (:md5, 1, :mtime) 
                ON DUPLICATE KEY UPDATE access_count = access_count + 1";
        try {
            $stmt = $pdo->prepare($sql);
            $stmt->execute([':md5' => $md5, ':mtime' => $mtime]);
        } catch (PDOException $e) {
            error_log("Increment failed: " . $e->getMessage());
        }
    }

    /**
     * 插入新緩存記錄(生成緩存時),包含文件修改時間
     */
    function insertCacheRecord($md5, $mtime) {
        $pdo = getDb();
        if (!$pdo) return;
        $sql = "INSERT INTO cache_stat (url_md5, access_count, cache_time) 
                VALUES (:md5, 0, :mtime) 
                ON DUPLICATE KEY UPDATE cache_time = :mtime, access_count = 0";
        try {
            $stmt = $pdo->prepare($sql);
            $stmt->execute([':md5' => $md5, ':mtime' => $mtime]);
        } catch (PDOException $e) {
            error_log("Insert record failed: " . $e->getMessage());
        }
    }

    // ========== 緩存路徑(新結構) ==========
    /**
     * 獲取緩存文件路徑
     * 目錄規則:cacheFile/{md5最後1位}/{md5最後2位}/{md5}.php
     */
    function getCacheFilePaths() {
        static $paths = null;
        if ($paths !== null) return $paths;
        $cacheKey = md5($_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
        $last1 = substr($cacheKey, -1);          // 第一層目錄(16個可能值)
        $last2 = substr($cacheKey, -2);          // 第二層目錄(16*16=256個可能值)
        $cacheDir = CACHE_ROOT . DIRECTORY_SEPARATOR . $last1 . DIRECTORY_SEPARATOR . $last2;
        $mainFile = $cacheDir . DIRECTORY_SEPARATOR . $cacheKey . CACHE_FIX;
        $paths = [
            'dir'      => $cacheDir,
            'main'     => $mainFile,
            'cacheKey' => $cacheKey,
        ];
        return $paths;
    }

    /**
     * 生成僞發佈時間(近三天內,同頁面同一天不變)
     */
    function getTime181Value() {
        $today = date('Y-m-d');
        $url = $_SERVER['REQUEST_URI'];
        $seed = $url . $today;
        $hash = md5($seed);
        $hex = substr($hash, 0, 8);
        $offset = hexdec($hex) % 172800; // 0~2天的秒數
        $base = strtotime($today . ' 00:00:00');
        $fakeTime = $base - $offset;
        return date('Y-m-d H:i:s', $fakeTime);
    }

    /**
     * 讀取主緩存內容(解壓)
     */
    function readMainCache($mainFile) {
        if (!file_exists($mainFile)) {
            return false;
        }
        $compressed = file_get_contents($mainFile);
        if ($compressed === false) {
            return false;
        }
        $decompressed = @gzuncompress($compressed);
        return ($decompressed !== false) ? $decompressed : false;
    }

    /**
     * 原子寫入緩存文件(先寫臨時文件,再 rename)
     */
    function atomicWriteCache($file, $data) {
        $dir = dirname($file);
        if (!file_exists($dir)) {
            if (!mkdir($dir, 0755, true)) {
                error_log("Cache mkdir failed: $dir");
                return false;
            }
            chmod($dir, 0755);
        }
        $tmpFile = $file . '.tmp.' . uniqid();
        if (file_put_contents($tmpFile, $data, LOCK_EX) === false) {
            return false;
        }
        if (!rename($tmpFile, $file)) {
            unlink($tmpFile);
            return false;
        }
        chmod($file, 0644);
        return true;
    }

    // ========== 緩存命中處理 ==========
    $paths = getCacheFilePaths();
    $mainFile = $paths['main'];
    $cacheKey = $paths['cacheKey'];

    if (file_exists($mainFile)) {
        $content = readMainCache($mainFile);
        if ($content !== false) {
            // 增加訪問計數,傳入文件修改時間
            $mtime = filemtime($mainFile);
            if ($mtime !== false) {
                incrementAccessCount($cacheKey, $mtime);
            } else {
                // 獲取失敗則用當前時間(降級)
                incrementAccessCount($cacheKey, time());
            }
            // 輸出替換佔位符
            $output = str_replace("{time181}", getTime181Value(), $content);
            echo $output;
            exit;
        }
        // 損壞文件刪除
        @unlink($mainFile);
    }

    // ========== 緩存未命中,生成新緩存 ==========
    if (!file_exists($paths['dir'])) {
        mkdir($paths['dir'], 0755, true);
        chmod($paths['dir'], 0755);
    }

    function AutoCache($contents) {
        global $paths;
        if (http_response_code() === 200 && !empty($contents)) {
            $compressed = gzcompress($contents, CACHE_COMPRESS_LEVEL);
            if ($compressed !== false) {
                if (atomicWriteCache($paths['main'], $compressed)) {
                    // 獲取剛寫入文件的修改時間
                    $mtime = filemtime($paths['main']);
                    if ($mtime !== false) {
                        insertCacheRecord($paths['cacheKey'], $mtime);
                    }
                } else {
                    error_log("Cache write failed for " . $paths['main']);
                }
            } else {
                error_log("Cache gzcompress failed for " . $paths['main']);
            }
        }
        return str_replace("{time181}", getTime181Value(), $contents);
    }

    ob_start('AutoCache');
}
?>

4.2 清理腳本 cache_cleanup.php

獨立 CLI 腳本,建議每天凌晨定時運行。

#!/usr/bin/env php
<?php
/**
 * 緩存清理腳本(高性能版本)
 * 階段1:刪除超時且訪問不足的記錄及文件(每天執行)
 * 階段2:若總記錄數超限,刪除最舊的記錄及文件(每天執行)
 * 階段3:掃描文件系統修復不一致(僅在週四執行,或手動強制)
 *         - 使用清單文件 + 批量 SQL,性能大幅提升
 */

// ========== 配置 ==========
define('CACHE_ROOT', dirname(__FILE__) . DIRECTORY_SEPARATOR . 'cacheFile');
define('CACHE_FIX', '.php');

define('DB_HOST', 'localhost');
define('DB_NAME', 'cache_music');
define('DB_USER', 'cache_music');
define('DB_PASS', 'eb7rydwKyaxRW7Gs');

define('RETAIN_DAYS', 7);
define('MIN_ACCESS', 20);
define('MAX_FILE_COUNT', 1000000);

// 階段3控制參數
define('BATCH_SIZE', 1000);          // 批量 SQL 操作的行數
define('DB_FETCH_SIZE', 5000);       // 每次從數據庫獲取的記錄數(遊標)
define('FLUSH_SIZE', 10000);         // 清單文件累積寫入閾值
define('SLEEP_USEC', 500000);        // 每批後暫停 0.5 秒

// ========== 數據庫函數 ==========
function getDb() {
    static $pdo = null;
    if ($pdo === null) {
        try {
            $pdo = new PDO(
                "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
                DB_USER,
                DB_PASS
            );
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        } catch (PDOException $e) {
            echo "DB connect failed: " . $e->getMessage() . "\n";
            exit(1);
        }
    }
    return $pdo;
}

// ========== 文件路徑與操作 ==========
function getCacheFilePath($md5) {
    $last1 = substr($md5, -1);
    $last2 = substr($md5, -2);
    return CACHE_ROOT . DIRECTORY_SEPARATOR . $last1 . DIRECTORY_SEPARATOR . $last2 . DIRECTORY_SEPARATOR . $md5 . CACHE_FIX;
}

function deleteFile($path) {
    return file_exists($path) && @unlink($path);
}

function getPhpFiles($dir) {
    $files = [];
    if (!is_dir($dir)) return $files;
    foreach (scandir($dir) as $item) {
        if ($item === '.' || $item === '..') continue;
        $path = $dir . DIRECTORY_SEPARATOR . $item;
        if (is_file($path) && substr($item, -4) === CACHE_FIX) {
            $files[] = $path;
        }
    }
    return $files;
}

function cleanEmptyDirs($dir) {
    if (!is_dir($dir)) return;
    foreach (scandir($dir) as $item) {
        if ($item === '.' || $item === '..') continue;
        $path = $dir . DIRECTORY_SEPARATOR . $item;
        if (is_dir($path)) cleanEmptyDirs($path);
    }
    if ($dir !== CACHE_ROOT && count(scandir($dir)) == 2) {
        @rmdir($dir);
    }
}

// ========== 數據庫記錄操作(批量) ==========
function deleteRecord($md5) {
    $pdo = getDb();
    $stmt = $pdo->prepare("DELETE FROM cache_stat WHERE url_md5 = ?");
    return $stmt->execute([$md5]);
}

function insertRecord($md5, $mtime) {
    $pdo = getDb();
    $stmt = $pdo->prepare("INSERT IGNORE INTO cache_stat (url_md5, access_count, cache_time) VALUES (?, 0, ?)");
    return $stmt->execute([$md5, $mtime]);
}

function countRecords() {
    return (int)getDb()->query("SELECT COUNT(*) FROM cache_stat")->fetchColumn();
}

function batchDeleteRecords($md5List) {
    if (empty($md5List)) return;
    $pdo = getDb();
    $placeholders = implode(',', array_fill(0, count($md5List), '?'));
    $sql = "DELETE FROM cache_stat WHERE url_md5 IN ($placeholders)";
    $stmt = $pdo->prepare($sql);
    $stmt->execute($md5List);
}

function batchInsertRecords($batch) {
    if (empty($batch)) return;
    $pdo = getDb();
    $values = [];
    $params = [];
    foreach ($batch as $item) {
        $values[] = "(?, 0, ?)";
        $params[] = $item['md5'];
        $params[] = $item['mtime'];
    }
    $sql = "INSERT IGNORE INTO cache_stat (url_md5, access_count, cache_time) VALUES " . implode(',', $values);
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
}

// ========== 階段1 ==========
function phase1() {
    $pdo = getDb();
    $threshold = time() - RETAIN_DAYS * 86400;
    $sql = "SELECT url_md5 FROM cache_stat WHERE cache_time < ? AND access_count < ?";
    $stmt = $pdo->prepare($sql);
    $stmt->execute([$threshold, MIN_ACCESS]);
    $md5List = $stmt->fetchAll(PDO::FETCH_COLUMN);
    $count = 0;
    foreach ($md5List as $md5) {
        $file = getCacheFilePath($md5);
        deleteFile($file);
        deleteRecord($md5);
        $count++;
    }
    echo "階段1刪除記錄與文件數: $count\n";
    return $count;
}

// ========== 階段2 ==========
function phase2() {
    $total = countRecords();
    if ($total <= MAX_FILE_COUNT) {
        echo "階段2:未超限,無需刪除\n";
        return 0;
    }
    $need = $total - MAX_FILE_COUNT;
    $pdo = getDb();
    // 修復:將 LIMIT ? 改爲直接拼接數字($need 爲整數,安全)
    $sql = "SELECT url_md5 FROM cache_stat ORDER BY cache_time ASC LIMIT " . intval($need);
    $stmt = $pdo->query($sql);
    $md5List = $stmt->fetchAll(PDO::FETCH_COLUMN);
    $count = 0;
    foreach ($md5List as $md5) {
        $file = getCacheFilePath($md5);
        deleteFile($file);
        deleteRecord($md5);
        $count++;
    }
    echo "階段2刪除記錄與文件數: $count\n";
    return $count;
}

// ========== 階段3(高性能批量版本) ==========
function phase3() {
    $pdo = getDb();
    $hexChars = ['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];
    $timeThreshold = time() - RETAIN_DAYS * 86400;

    // ----- 第一步:生成每個二級目錄的清單文件(記錄數據庫中的 MD5) -----
    echo "[" . date('Y-m-d H:i:s') . "] 開始生成清單文件...\n";
    $listFiles = []; // 記錄所有生成的清單文件路徑,便於後續清理
    $dirBuffers = [];        // 按目錄分組的數據:['dirPath' => [md5, md5, ...]]
    $lastMd5 = '';
    $totalDbRecords = 0;

    // 遊標分批獲取數據庫記錄(修正 LIMIT 拼接)
    while (true) {
        $sql = "SELECT url_md5 FROM cache_stat WHERE url_md5 > ? ORDER BY url_md5 LIMIT " . intval(DB_FETCH_SIZE);
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$lastMd5]);
        $rows = $stmt->fetchAll(PDO::FETCH_COLUMN);
        if (empty($rows)) break;

        foreach ($rows as $md5) {
            // 計算所屬二級目錄路徑(與 cache.php 規則一致)
            $last1 = substr($md5, -1);
            $last2 = substr($md5, -2);
            $dirPath = CACHE_ROOT . DIRECTORY_SEPARATOR . $last1 . DIRECTORY_SEPARATOR . $last2;

            // 按目錄分組
            if (!isset($dirBuffers[$dirPath])) {
                $dirBuffers[$dirPath] = [];
            }
            $dirBuffers[$dirPath][] = $md5;
            $totalDbRecords++;
            $lastMd5 = $md5;

            // 如果累積數據達到 FLUSH_SIZE,則寫入清單文件
            $totalInBuffer = array_sum(array_map('count', $dirBuffers));
            if ($totalInBuffer >= FLUSH_SIZE) {
                foreach ($dirBuffers as $dir => $md5List) {
                    $listFile = $dir . DIRECTORY_SEPARATOR . '.cache_list.txt';
                    file_put_contents($listFile, implode("\n", $md5List) . "\n", FILE_APPEND | LOCK_EX);
                    $listFiles[$listFile] = true;
                }
                $dirBuffers = []; // 清空緩衝區
            }
        }
    }

    // 寫入剩餘緩衝區數據
    foreach ($dirBuffers as $dir => $md5List) {
        $listFile = $dir . DIRECTORY_SEPARATOR . '.cache_list.txt';
        file_put_contents($listFile, implode("\n", $md5List) . "\n", FILE_APPEND | LOCK_EX);
        $listFiles[$listFile] = true;
    }
    echo "[" . date('Y-m-d H:i:s') . "] 清單文件生成完成,共處理數據庫記錄 {$totalDbRecords} 條。\n";

    // ----- 第二步:遍歷所有二級目錄,對比清單與實際文件 -----
    echo "[" . date('Y-m-d H:i:s') . "] 開始對比目錄...\n";
    $insertBatch = [];      // 待批量插入的記錄 ['md5' => $md5, 'mtime' => $mtime]
    $deleteRecordBatch = []; // 待批量刪除的 MD5
    $deleteFileCount = 0;   // 刪除文件計數(直接unlink)
    $processed = 0;

    foreach ($hexChars as $l1) {
        $dir1 = CACHE_ROOT . DIRECTORY_SEPARATOR . $l1;
        if (!is_dir($dir1)) continue;
        foreach ($hexChars as $l2) {
            $dir2 = $dir1 . DIRECTORY_SEPARATOR . $l2 . $l1;
            if (!is_dir($dir2)) continue;

            // 讀取該目錄的清單文件
            $listFile = $dir2 . DIRECTORY_SEPARATOR . '.cache_list.txt';
            $listMd5s = [];
            if (file_exists($listFile)) {
                $content = file_get_contents($listFile);
                if ($content !== false) {
                    $listMd5s = array_filter(explode("\n", $content), 'strlen');
                }
            }
            $listMap = array_flip($listMd5s);

            // 獲取實際文件列表
            $files = getPhpFiles($dir2);
            $actualMd5s = [];
            foreach ($files as $file) {
                $md5 = basename($file, CACHE_FIX);
                $actualMd5s[$md5] = $file;
            }

            // 找出清單中有但實際文件缺失的記錄
            foreach ($listMd5s as $md5) {
                if (!isset($actualMd5s[$md5])) {
                    $deleteRecordBatch[] = $md5;
                    if (count($deleteRecordBatch) >= BATCH_SIZE) {
                        batchDeleteRecords($deleteRecordBatch);
                        $deleteRecordBatch = [];
                        usleep(SLEEP_USEC);
                    }
                }
            }

            // 找出實際文件存在但清單中沒有的文件
            foreach ($actualMd5s as $md5 => $file) {
                if (!isset($listMap[$md5])) {
                    $mtime = filemtime($file);
                    if ($mtime === false || $mtime < $timeThreshold) {
                        deleteFile($file);
                        $deleteFileCount++;
                    } else {
                        $insertBatch[] = ['md5' => $md5, 'mtime' => $mtime];
                        if (count($insertBatch) >= BATCH_SIZE) {
                            batchInsertRecords($insertBatch);
                            $insertBatch = [];
                            usleep(SLEEP_USEC);
                        }
                    }
                }
                $processed++;
                if ($processed % 1000 == 0) {
                    echo "階段3進度:已處理 {$processed} 個文件對比,累計刪除文件 {$deleteFileCount}\n";
                }
            }

            // 刪除該目錄的清單文件(處理完即刪)
            if (file_exists($listFile)) {
                @unlink($listFile);
                unset($listFiles[$listFile]);
            }
        }
    }

    // 處理剩餘批次
    if (!empty($insertBatch)) {
        batchInsertRecords($insertBatch);
        $insertBatch = [];
    }
    if (!empty($deleteRecordBatch)) {
        batchDeleteRecords($deleteRecordBatch);
        $deleteRecordBatch = [];
    }

    // 清理可能殘留的清單文件
    foreach ($listFiles as $file => $dummy) {
        if (file_exists($file)) @unlink($file);
    }

    echo "階段3完成:處理文件 {$processed} 個,刪除文件 {$deleteFileCount} 個。\n";
}

// ========== 主流程 ==========
function cleanup($forcePhase3 = false) {
    echo "[" . date('Y-m-d H:i:s') . "] 開始清理...\n";
    phase1();
    phase2();

    $isThursday = (date('N') == 4);
    if ($forcePhase3 || $isThursday) {
        echo "[" . date('Y-m-d H:i:s') . "] 執行階段3(全量修復)...\n";
        phase3();
    } else {
        echo "[" . date('Y-m-d H:i:s') . "] 今日不是週四,跳過階段3(全量修復)。\n";
    }

    cleanEmptyDirs(CACHE_ROOT);
    echo "[" . date('Y-m-d H:i:s') . "] 清理完成。\n";
}

// ========== 腳本入口 ==========
if (php_sapi_name() === 'cli') {
    ob_implicit_flush(true);
    if (ob_get_level()) ob_end_flush();

    $force = false;
    global $argv;
    if (isset($argv)) {
        foreach ($argv as $arg) {
            if ($arg === '--force' || $arg === '-f') {
                $force = true;
                break;
            }
        }
    }
    cleanup($force);
} else {
    header('Content-Type: text/plain; charset=utf-8');
    echo "僅允許 CLI 運行。\n";
    exit(1);
}
?>

五、部署與定時任務

  1. 將 cache.php 放置到項目合適位置,並確保它在所有輸出之前被包含。

  2. 執行建表 SQL

  3. 將 cache_cleanup.php 放到服務器,賦予執行權限。

  4. 添加 crontab 任務(每天凌晨 4 點執行):

    cd /data/www/wwwroot/123.com && /usr/bin/php cache_cleanup.php

六、性能優化與注意事項

  • 目錄分散:利用 MD5 後兩位,將文件均勻散列到 256 個二級目錄,單目錄文件數可控(總文件數/256)。

  • 數據庫索引cache_time 和 access_count 索引保證了階段一和階段二的查詢效率。

  • 階段三速度控制BATCH_SIZE 和 SLEEP_USEC 可調,避免 I/O 突增。

  • 內存使用:階段三將全部 MD5 加載到 $dbMap,100 萬條記錄約佔用 60-80 MB,需確保 PHP 內存限制足夠。

  • 異常處理:所有數據庫操作都捕獲異常並記錄日誌,不會導致腳本中斷。

七、總結

這套緩存系統結合了文件緩存的性能和 MySQL 計數的靈活性,實現了基於熱度的智能清理。三階段清理策略既能有效淘汰冷門緩存,又能控制總容量,並提供了完善的修復機制。

經過改造,緩存目錄不再包含無用的 pageType 層級,路徑規則統一,清理腳本的複雜度大幅降低。在實際運行中,即使文件數達到百萬級別,階段一和階段二也能在幾秒內完成,階段三在低峯期勻速掃描,對服務器壓力極小。

如果你也面臨類似的緩存管理困擾,不妨參考這個方案,根據自身需求調整閾值參數即可。

微信赞赏

微信

支付宝赞赏

支付寶

✍️ 作者: 紅穆

網站管理員 · 感謝閱讀,更多精彩內容敬請關注

作者主頁 查看主頁 →

相關文章

浏览器缓存增加你网站二次访问速度

瀏覽器緩存增加你網站二次訪問速度 踩坑總結

使用瀏覽器緩存官方話語:如果用戶會多次訪問您的網站,那麼靜態資源的瀏覽器緩存可以節省用戶的時間。緩存標頭應當應用到所有可緩存的靜態資源中,而不僅僅是應用到一小部分靜態資源(例如,圖片)中。可緩存的資源包括JS和CSS文件、圖像文件及其他二進制對象文件(媒體文件和PDF文件等)。通常情況下,HTML不…
👁 202
常用正则表达式

常用正則表達式 踩坑總結

正則表達式網址(URL)[a-zA-z]+://[^\s]*IP地址(IP Address)((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)電子郵件(Email)\w+([-+.]\w+)*@\w+([-.]\w+)*…
👁 243

推薦閱讀

(自适应手机端)百度MIP建筑装饰装修类网站pbootcms模板 幕墙材料网站源码下载 0369

(自適應手機端)百度MIP建築裝飾裝修類網站pbootcms模板 幕牆材料網站源碼下載 0369 實用收藏 pbootcms模板

本套自適應手機端的百度MIP建築裝飾裝修類PbootCMS網站模板。設計風格專業現代,適合展示幕牆材料、裝修工程及裝飾案例。支持百度MIP有助於提升移動端SEO效果,吸引更多流量。模板展示 安裝說明 網站後臺:/admin.php 賬號:admin 密碼:admin 解壓密碼:www.4s5.cn相…
👁 38
响应式家用电器厨卫电器网站模板 0922

響應式家用電器廚衛電器網站模板 0922 實用收藏 易優模板

此套eyoucms響應式模板適用於家用電器與廚衛電器行業,設計風格現代實用,能夠展示家電產品、廚衛電器、品牌形象及新品發佈。有助於家電品牌在線上展示產品,吸引家庭消費者。模板展示 安裝說明 網站後臺:/login.php 賬號:admin 密碼:admin 相關文章易優CMS安裝常見問題總結易優CM…
👁 45
响应式网站建设设计类网站模板 0410

響應式網站建設設計類網站模板 0410 實用收藏 易優模板

此套eyoucms響應式模板適用於網站建設與設計類行業,設計風格現代科技,能夠展示建站案例、設計作品、技術服務及客戶評價。有助於網站建設公司在線上吸引有建站需求的企業客戶。模板展示 安裝說明 網站後臺:/login.php 賬號:admin 密碼:admin 相關文章易優CMS安裝常見問題總結易優C…
👁 60
帝国cms文章多了后台卡成狗解决办法

帝國cms文章多了後臺卡成狗解決辦法 程序筆記 帝國cms

最近帝國文章上百萬,於是後臺加載一下頁面,發現非常的卡頓 幾乎所有頁面都會卡頓,於是乎我排查了一下問題 主要原因是帝國存在一個頁面刷新的加載文件,當文章多了後,加載這個頁面刷新的PHP文件就會非常慢 解決辦法找到那個頁面刷新的文件,然後解決他,主要是我解決問題之後,搞忘記記錄那個文件名了,現在再去找…
👁 382
(PC+WAP)红色破碎设备网站模板 通用机械设备网站源码下载 1090

(PC+WAP)紅色破碎設備網站模板 通用機械設備網站源碼下載 1090 實用收藏 pbootcms模板

一款紅色破碎設備與通用機械設備PbootCMS網站模板,支持PC與WAP端。設計風格熱情專業,適合展示破碎機設備、礦山機械及工程案例。有助於礦山設備企業在線上展示產品,吸引礦業與建築客戶。模板展示 安裝說明 網站後臺:/admin.php 賬號:admin 密碼:admin 解壓密碼:www.4s5…
👁 36