紅穆ノート
レコメンド 落とし穴のまとめ 高性能PHPキャッシュシステム設計:スマートなクリアリングとカウント管理
落とし穴のまとめ

高性能PHPキャッシュシステム設計:スマートなクリアリングとカウント管理

高性能PHPキャッシュシステム設計:スマートなクリアリングとカウント管理

 Webプロジェクトにおいて、ページキャッシュはパフォーマンス向上のための一般的な手法です。以前は、シンプルなファイルキャッシュプラグインを使用していました【EmpireCMS用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 清掃戦略(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 &nbsp;と&nbsp; access_count  索引保证了阶段一和阶段二的查询效率。

  • 阶段三速度控制BATCH_SIZE &nbsp;と&nbsp; SLEEP_USEC  可调,避免 I/O 突增。

  • 内存使用:阶段三将全部 MD5 加载到  $dbMap,100 万条记录约占用 60-80 MB,需确保 PHP 内存限制足够。

  • 异常处理:所有数据库操作都捕获异常并记录日志,不会导致脚本中断。

七、総括

这套缓存系统结合了文件缓存的性能和 MySQL 计数的灵活性,实现了基于热度的智能清理。三阶段清理策略既能有效淘汰冷门缓存,又能控制总容量,并提供了完善的修复机制。

经过改造,缓存目录不再包含无用的  pageType  层级,路径规则统一,清理脚本的复杂度大幅降低。在实际运行中,即使文件数达到百万级别,阶段一和阶段二也能在几秒内完成,阶段三在低峰期匀速扫描,对服务器压力极小。

如果你也面临类似的缓存管理困扰,不妨参考这个方案,根据自身需求调整阈值参数即可。

微信赞赏

WeChat

支付宝赞赏

Alipay

✍️ クリエイター: 紅穆

ウェブサイト管理者 · ご覧いただきありがとうございます!さらに多くの魅力的なコンテンツをお楽しみいただけますよう、引き続きご支援をお願いいたします。

著者プロフィール プロフィールを見る →

関連記事

ブラウザのキャッシュ利用により、お客様のウェブサイトへの再アクセス速度が向上します。

ブラウザのキャッシュ利用により、お客様のウェブサイトへの再アクセス速度が向上します。 落とし穴のまとめ

ブラウザキャッシュの活用:ユーザーが同サイトを複数回アクセスする場合、静的リソースのブラウザキャッシュを活用することで、ユーザーの時間の節約が可能になります。キャッシュヘッダーは、画像などの一部の静的リソースに限定せず、すべてのキャッシュ可能な静的リソースに適用されるべきです。キャッシュ可能なリソースには、JSファイルやCSSファイル、画像ファイル、その他のバイナリ形式のファイル(メディアファイルやPDFファイルなど)が含まれます。通常、HTML自体は…
👁 208
よく使用される正規表現

よく使用される正規表現 落とし穴のまとめ

正規表現による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+)*…
👁 249
`file_get_contents` ページで表示内容が乱れている場合の解決方法

`file_get_contents` ページで表示内容が乱れている場合の解決方法 落とし穴のまとめ

私のページでは UTF-8を使用していますが、`file_get_contents`で取得したページデータはGB2312です。この場合、中文字が乱コードとして表示されます。解決方法は以下の通りです:以下のコードをコピーしてください:`<?php header("Content-Type: text/html; charset=utf-8"); $keyworld = "煤层"; …`
👁 205

おすすめ読書

emlogの管理コンソールで記事を編集する際、自動保存および文章数の統計表示が実行されます。

emlogの管理コンソールで記事を編集する際、自動保存および文章数の統計表示が実行されます。 プログラムノート emlog

具体的な変更内容は図に示されています。具体的には、保存された秒数を表示します。同時に、文章の文字数も表示されるため、ユーザーが自身の記事を簡単に確認できるようになっています!変更手順:ファイル「/admin/views/article_write.php」を開き、以下のコード箇所を検索してください:`<label>記事要約:</label> <%/label>`(約18行)。このコード箇所に以下のコードを追加してください:`<span i…>`
👁 268
(自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载 0288

(自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载 0288 実用収集 pbootcmsテンプレート

一款自适应手机端的响应式新闻博客与自媒体运营PbootCMS网站模板。设计风格简洁信息流,适合自媒体人分享行业动态、运营技巧及个人观点。有助于自媒体人在线上建立影响力与粉丝群体。模板展示 安装说明 网站后台:/admin.php 账号:admin 密码:admin 解压密码:www.4s5.cn相关…
👁 67
(PC+WAP)PBOOTCMS高端餐饮美食加盟网站模板 美食小吃公司加盟网站源码下载 0550

(PC+WAP)PBOOTCMS高端餐饮美食加盟网站模板 美食小吃公司加盟网站源码下载 0550 実用収集 pbootcmsテンプレート

一款专为餐饮美食加盟与小吃公司设计的高端PbootCMS网站模板,同时支持PC和移动端。页面设计大气且富有吸引力,能够很好地展示品牌特色、加盟优势及成功案例。模板内置加盟留言、在线咨询等功能,是餐饮品牌开展线上招商加盟业务的得力工具。模板展示 安装说明 网站后台:/admin.php 账号:admi…
👁 56
多段階フィルタリング機能搭載 –(スマートフォン向け適応型)レスポンシブ設計の海外留学相談・教育培训机构向けPBootCMSウェブサイトテンプレートをダウンロード – 0709

多段階フィルタリング機能搭載 –(スマートフォン向け適応型)レスポンシブ設計の海外留学相談・教育培训机构向けPBootCMSウェブサイトテンプレートをダウンロード – 0709 実用収集 pbootcmsテンプレート

階層別フィルタリング機能を搭載し、スマートフォン端末向けに自動適応するPbootCMSウェブサイトテンプレートです。このテンプレートは、海外留学相談サービスおよび教育・トレーニング機関向けに特別に設計されています。レスポンシブデザインにより、PCおよびスマートフォンでの快適な利用体験が実現されており、階層別フィルタリング機能によりユーザーは海外留学プログラムやコース情報を簡単に検索できます。留学仲介機関や教育機関において、ユーザー体験の向上とコンバージョン率の向上に最適な選択肢です。テンプレート表示・インストール手順:ウェブサイト管理画面:/admin.php、ユーザー名:adm…
👁 51