Skip to content

OPcache 优化策略

概述

仅仅启用 OPcache 并不足以发挥其最大价值。在生产环境中,通过合理的缓存预热、命中率监控、缓存失效处理等优化策略,可以显著提升 PHP 应用的响应速度和稳定性。本章将深入探讨 OPcache 的优化方法,帮助你在各种场景下获得最佳性能表现。

前置知识

阅读本节前,建议先了解:OPcache 配置详解

基础概念

缓存命中率

缓存命中率(Hit Rate)是衡量 OPcache 效果的核心指标:

命中率 = 缓存命中次数 / (缓存命中次数 + 缓存未命中次数) × 100%

一个健康的生产环境应保持 95% 以上的命中率。低于 95% 通常意味着配置不当或频繁的缓存失效。

缓存失效场景

OPcache 缓存会在以下情况失效:

  1. 文件修改:PHP 文件内容发生变化(validate_timestamps = 1 时)
  2. 手动清除:调用 opcache_reset()opcache_invalidate()
  3. 内存溢出:共享内存不足,旧缓存被淘汰
  4. 进程重启:PHP-FPM 重启或重新加载

缓存预热

缓存预热(Warmup)是在应用启动或部署后,提前将关键 PHP 文件编译并缓存到 OPcache 中,避免用户首次请求时的编译延迟。

详细说明

缓存命中率监控

opcache_get_status() 详解

php
<?php
declare(strict_types=1);

/**
 * 获取 OPcache 完整状态信息
 */
function getOpcacheDetailedStatus(): array
{
    $status = opcache_get_status(false);
    if ($status === false) {
        throw new RuntimeException('OPcache 未启用');
    }

    return [
        // 缓存统计
        'hit_rate' => $status['opcache_statistics']['opcache_hit_rate'],
        'hits' => $status['opcache_statistics']['hits'],
        'misses' => $status['opcache_statistics']['misses'],
        'num_cached_scripts' => $status['opcache_statistics']['num_cached_scripts'],
        'num_cached_keys' => $status['opcache_statistics']['num_cached_keys'],
        'max_cached_keys' => $status['opcache_statistics']['max_cached_keys'],

        // 内存统计
        'used_memory' => $status['memory_usage']['used_memory'],
        'free_memory' => $status['memory_usage']['free_memory'],
        'wasted_memory' => $status['memory_usage']['wasted_memory'],
        'total_memory' => $status['memory_usage']['used_memory']
            + $status['memory_usage']['free_memory'],

        // 重启统计
        'oom_restarts' => $status['opcache_statistics']['oom_restarts'],
        'hash_restarts' => $status['opcache_statistics']['hash_restarts'],
        'manual_restarts' => $status['opcache_statistics']['manual_restarts'],

        // JIT 统计
        'jit' => $status['jit'] ?? null,
    ];
}

$status = getOpcacheDetailedStatus();

echo "命中率: {$status['hit_rate']}%" . PHP_EOL;
echo "已缓存脚本: {$status['num_cached_scripts']}" . PHP_EOL;
echo "OOM 重启: {$status['oom_restarts']}" . PHP_EOL;

命中率阈值告警

php
<?php
declare(strict_types=1);

/**
 * OPcache 健康检查类
 */
class OpcacheHealthChecker
{
    private const WARN_HIT_RATE = 95.0;
    private const CRITICAL_HIT_RATE = 90.0;
    private const WARN_MEMORY_USAGE = 80.0;
    private const CRITICAL_MEMORY_USAGE = 95.0;

    public function check(): array
    {
        $status = opcache_get_status(false);
        if ($status === false) {
            return [
                'status' => 'critical',
                'message' => 'OPcache 未启用',
            ];
        }

        $stats = $status['opcache_statistics'];
        $mem = $status['memory_usage'];
        $issues = [];

        // 检查命中率
        $hitRate = (float) $stats['opcache_hit_rate'];
        if ($hitRate < self::CRITICAL_HIT_RATE) {
            $issues[] = [
                'level' => 'critical',
                'message' => sprintf(
                    '命中率 %.2f%% 低于临界值 %.1f%%',
                    $hitRate,
                    self::CRITICAL_HIT_RATE
                ),
            ];
        } elseif ($hitRate < self::WARN_HIT_RATE) {
            $issues[] = [
                'level' => 'warning',
                'message' => sprintf(
                    '命中率 %.2f%% 低于警告值 %.1f%%',
                    $hitRate,
                    self::WARN_HIT_RATE
                ),
            ];
        }

        // 检查内存使用率
        $totalMem = $mem['used_memory'] + $mem['free_memory'];
        $usedPercent = ($mem['used_memory'] / $totalMem) * 100;
        if ($usedPercent > self::CRITICAL_MEMORY_USAGE) {
            $issues[] = [
                'level' => 'critical',
                'message' => sprintf(
                    '内存使用率 %.1f%% 超过临界值 %.1f%%',
                    $usedPercent,
                    self::CRITICAL_MEMORY_USAGE
                ),
            ];
        } elseif ($usedPercent > self::WARN_MEMORY_USAGE) {
            $issues[] = [
                'level' => 'warning',
                'message' => sprintf(
                    '内存使用率 %.1f%% 超过警告值 %.1f%%',
                    $usedPercent,
                    self::WARN_MEMORY_USAGE
                ),
            ];
        }

        // 检查 OOM 重启
        if ($stats['oom_restarts'] > 0) {
            $issues[] = [
                'level' => 'critical',
                'message' => "内存溢出重启 {$stats['oom_restarts']} 次",
            ];
        }

        // 检查哈希表重启
        if ($stats['hash_restarts'] > 0) {
            $issues[] = [
                'level' => 'warning',
                'message' => "哈希表溢出重启 {$stats['hash_restarts']} 次",
            ];
        }

        return [
            'status' => empty($issues) ? 'healthy' : 'unhealthy',
            'hit_rate' => $hitRate,
            'memory_usage_percent' => round($usedPercent, 2),
            'issues' => $issues,
        ];
    }
}

缓存失效处理

opcache_invalidate() 使用

php
<?php
declare(strict_types=1);

/**
 * 文件更新后精确失效 OPcache 缓存
 */
function invalidateFileCache(string $filePath): bool
{
    // 检查文件是否存在
    if (!file_exists($filePath)) {
        return false;
    }

    // 强制失效该文件的缓存(即使 validate_timestamps=0 也能工作)
    return opcache_invalidate($filePath, true);
}

/**
 * 批量失效缓存(部署时使用)
 */
function invalidateDirectoryCache(string $directory, bool $force = true): int
{
    $count = 0;
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator(
            $directory,
            RecursiveDirectoryIterator::SKIP_DOTS
        )
    );

    foreach ($iterator as $file) {
        /** @var SplFileInfo $file */
        if ($file->isFile() && $file->getExtension() === 'php') {
            if (opcache_invalidate($file->getPathname(), $force)) {
                $count++;
            }
        }
    }

    return $count;
}

// 使用示例:更新单个文件后
invalidateFileCache('/var/www/html/app/Services/UserService.php');

// 使用示例:部署新版本后批量失效
$invalidated = invalidateDirectoryCache('/var/www/html/app', true);
echo "已失效 {$invalidated} 个文件的缓存" . PHP_EOL;

opcache_reset() 使用

php
<?php
declare(strict_types=1);

/**
 * 重置所有 OPcache 缓存
 * 注意:这会影响所有 PHP 进程
 */
function resetAllCache(): void
{
    if (!opcache_reset()) {
        throw new RuntimeException('OPcache 重置失败');
    }

    echo "所有 OPcache 缓存已重置" . PHP_EOL;
}

::: danger opcache_reset 的代价
`opcache_reset()` 会清除所有缓存,之后所有请求都需要重新编译。在生产环境中应谨慎使用,优先考虑 `opcache_invalidate()` 进行精确失效。
:::

部署集成失效

php
<?php
declare(strict_types=1);

/**
 * 部署后的缓存管理器
 */
class DeploymentCacheManager
{
    public function __construct(
        private string $projectRoot,
        private array $watchedDirs = [],
    ) {
    }

    /**
     * 部署后刷新缓存
     */
    public function refreshAfterDeploy(): void
    {
        echo "开始部署后缓存刷新..." . PHP_EOL;

        // 策略1:仅刷新变更的文件
        $changedFiles = $this->getChangedFiles();
        foreach ($changedFiles as $file) {
            opcache_invalidate($file, true);
            echo "失效: {$file}" . PHP_EOL;
        }

        // 策略2:如果 validate_timestamps=0,需要刷新所有 watched 目录
        if (ini_get('opcache.validate_timestamps') === '0') {
            foreach ($this->watchedDirs as $dir) {
                $fullPath = $this->projectRoot . '/' . $dir;
                if (is_dir($fullPath)) {
                    $this->invalidateDirectory($fullPath);
                }
            }
        }

        // 策略3:预热关键文件
        $this->warmupCriticalFiles();

        echo "缓存刷新完成" . PHP_EOL;
    }

    private function getChangedFiles(): array
    {
        // 可与 Git 结合获取变更文件列表
        // 此处为简化示例
        return [];
    }

    private function invalidateDirectory(string $dir): void
    {
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
        );

        foreach ($iterator as $file) {
            /** @var SplFileInfo $file */
            if ($file->isFile() && $file->getExtension() === 'php') {
                opcache_invalidate($file->getPathname(), true);
            }
        }
    }

    private function warmupCriticalFiles(): void
    {
        // 预热入口文件和常用服务
        $criticalFiles = [
            $this->projectRoot . '/public/index.php',
            $this->projectRoot . '/app/Providers/AppServiceProvider.php',
        ];

        foreach ($criticalFiles as $file) {
            if (file_exists($file)) {
                opcache_compile_file($file);
            }
        }
    }
}

缓存预热策略

按需预热

php
<?php
declare(strict_types=1);

/**
 * 智能缓存预热器
 * 优先预热访问频率最高的文件
 */
class SmartCacheWarmer
{
    /**
     * 基于访问日志预热
     */
    public function warmupByAccessLog(string $logFile, string $docRoot, int $limit = 100): array
    {
        $fileAccessCount = [];
        $handle = fopen($logFile, 'r');

        if ($handle === false) {
            throw new RuntimeException("无法打开日志文件: {$logFile}");
        }

        while (($line = fgets($handle)) !== false) {
            // 解析 Nginx/Apache 访问日志获取 PHP 文件路径
            if (preg_match('/GET\s+(\S+\.php)\s/', $line, $matches)) {
                $path = $matches[1];
                $fileAccessCount[$path] = ($fileAccessCount[$path] ?? 0) + 1;
            }
        }
        fclose($handle);

        // 按访问频率排序
        arsort($fileAccessCount);
        $topFiles = array_slice(array_keys($fileAccessCount), 0, $limit);

        // 预热文件
        $results = ['success' => 0, 'failed' => 0];
        foreach ($topFiles as $path) {
            $fullPath = $docRoot . $path;
            if (file_exists($fullPath)) {
                if (opcache_compile_file($fullPath)) {
                    $results['success']++;
                } else {
                    $results['failed']++;
                }
            }
        }

        return $results;
    }

    /**
     * 基于 Composer autoload 预热
     */
    public function warmupByAutoload(string $vendorDir): int
    {
        $count = 0;
        $classMapFile = $vendorDir . '/composer/autoload_classmap.php';

        if (!file_exists($classMapFile)) {
            // 如果没有 classmap,生成一个
            exec("cd {$vendorDir}/.. && composer dump-autoload --optimize");
        }

        // 加载 classmap
        $classMap = require $classMapFile;

        foreach ($classMap as $class => $file) {
            if (file_exists($file) && opcache_compile_file($file)) {
                $count++;
            }
        }

        return $count;
    }
}

预热 Web 界面(管理面板)

php
<?php
declare(strict_types=1);

/**
 * OPcache 管理接口
 * 注意:生产环境必须加访问控制!
 */
class OpcacheAdminController
{
    /**
     * 获取缓存状态(API 接口)
     */
    public function status(): array
    {
        $this->ensureCliOrAdmin();

        $status = opcache_get_status(false);
        return [
            'enabled' => $status !== false,
            'memory' => $status['memory_usage'] ?? null,
            'statistics' => $status['opcache_statistics'] ?? null,
            'jit' => $status['jit'] ?? null,
            'config' => $this->getConfig(),
        ];
    }

    /**
     * 失效单个文件
     */
    public function invalidate(string $file): bool
    {
        $this->ensureCliOrAdmin();
        $fullPath = $this->resolvePath($file);

        return opcache_invalidate($fullPath, true);
    }

    /**
     * 重置所有缓存
     */
    public function reset(): void
    {
        $this->ensureCliOrAdmin();

        if (PHP_SAPI !== 'cli') {
            opcache_reset();
            echo "OPcache 已重置" . PHP_EOL;
        }
    }

    /**
     * 获取所有已缓存的脚本列表
     */
    public function cachedScripts(): array
    {
        $this->ensureCliOrAdmin();

        $status = opcache_get_status(false);
        return $status['opcache_statistics']['cached_scripts'] ?? [];
    }

    private function ensureCliOrAdmin(): void
    {
        if (PHP_SAPI === 'cli') {
            return;
        }
        // 这里应加入实际的权限验证逻辑
        // 例如检查 IP 白名单、Admin Token 等
    }

    private function resolvePath(string $relativePath): string
    {
        $realpath = realpath($relativePath);
        if ($realpath === false) {
            throw new InvalidArgumentException("文件不存在: {$relativePath}");
        }
        return $realpath;
    }

    private function getConfig(): array
    {
        $directives = [
            'opcache.enable',
            'opcache.memory_consumption',
            'opcache.interned_strings_buffer',
            'opcache.max_accelerated_files',
            'opcache.validate_timestamps',
            'opcache.save_comments',
            'opcache.jit',
            'opcache.jit_buffer_size',
        ];

        $config = [];
        foreach ($directives as $dir) {
            $config[$dir] = ini_get($dir);
        }
        return $config;
    }
}

生产环境最佳配置

完整优化配置

ini
; /etc/php/8.1/fpm/conf.d/99-opcache-production.ini

[OPcache]
; === 基本开关 ===
opcache.enable = 1
opcache.enable_cli = 0
opcache.enable_dl = 0

; === 内存配置 ===
; 操作码缓存内存:根据项目实际需求调整
opcache.memory_consumption = 512
; 内部字符串缓冲区
opcache.interned_strings_buffer = 32
; 最大缓存文件数
opcache.max_accelerated_files = 40000

; === 缓存验证 ===
; 生产环境关闭时间戳验证(提升 5-15% 性能)
opcache.validate_timestamps = 0
opcache.revalidate_freq = 0

; === 代码优化 ===
; 启用所有优化
opcache.optimization_level = 0x7FFFBFFF
; 保留注释(框架依赖)
opcache.save_comments = 1
; 快速关闭
opcache.fast_shutdown = 1

; === 文件缓存 ===
; 启用磁盘缓存,加速 PHP-FPM 重启后的首次请求
opcache.file_cache = /var/cache/php/opcache
opcache.file_cache_only = 0
; 启用一致性检查
opcache.file_cache_consistency_checks = 1

; === JIT ===
; 使用 tracing 模式获得最佳性能
opcache.jit = 1254
opcache.jit_buffer_size = 256M

; === 保护 ===
; 黑名单
opcache.blacklist_filename = /etc/php/opcache-blacklist.txt
; 禁止超大文件缓存(保护内存)
opcache.max_file_size = 0
; 保护模式(限制 opcache_* 函数在脚本中调用)
; opcache.restrict_api = /var/www/html/admin/

FPM 进程池整合

ini
; /etc/php/8.1/fpm/pool.d/www.conf 中整合 OPcache 管理

; 使用 ondemand PM 时注意 OPcache 预热
; dynamic PM 通常更适合 OPcache 场景

[www]
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 20
pm.max_requests = 500

; OPcache 预热脚本(FPM 启动时执行)
; php-fpm 本身不支持 preload,但可以通过 systemd ExecStartPost 实现

systemd 服务集成预热

ini
# /etc/systemd/system/php8.1-fpm.service.d/opcache-warmup.conf

[Service]
# FPM 启动后执行预热
ExecStartPost=/usr/bin/php /var/www/html/scripts/opcache-warmup.php

实战示例

Nginx + PHP-FPM 负载测试对比

php
<?php
declare(strict_types=1);

/**
 * 性能基准测试脚本
 * 用于对比 OPcache 启用前后的性能差异
 */
class OpcacheBenchmark
{
    private int $iterations;
    private string $targetUrl;

    public function __construct(string $targetUrl, int $iterations = 100)
    {
        $this->targetUrl = $targetUrl;
        $this->iterations = $iterations;
    }

    /**
     * 使用 curl_multi 并发测试
     */
    public function run(): array
    {
        $times = [];
        $mh = curl_multi_init();

        // 分批处理避免资源耗尽
        $batchSize = 10;
        for ($i = 0; $i < $this->iterations; $i += $batchSize) {
            $handles = [];
            for ($j = 0; $j < $batchSize && ($i + $j) < $this->iterations; $j++) {
                $ch = curl_init($this->targetUrl);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_HEADER, true);
                curl_setopt($ch, CURLOPT_NOBODY, false);
                curl_multi_add_handle($mh, $ch);
                $handles[] = $ch;
            }

            // 等待所有请求完成
            do {
                $status = curl_multi_exec($mh, $active);
            } while ($status === CURLM_CALL_MULTI_PERFORM && $active);

            // 收集结果
            foreach ($handles as $ch) {
                $info = curl_getinfo($ch);
                $times[] = $info['total_time'];
                curl_multi_remove_handle($mh, $ch);
                curl_close($ch);
            }
        }

        curl_multi_close($mh);
        return $this->analyzeResults($times);
    }

    private function analyzeResults(array $times): array
    {
        sort($times);
        $count = count($times);
        $sum = array_sum($times);

        return [
            'total_requests' => $count,
            'avg' => round($sum / $count * 1000, 2) . 'ms',
            'min' => round(min($times) * 1000, 2) . 'ms',
            'max' => round(max($times) * 1000, 2) . 'ms',
            'p50' => round($times[(int) ($count * 0.50)] * 1000, 2) . 'ms',
            'p90' => round($times[(int) ($count * 0.90)] * 1000, 2) . 'ms',
            'p99' => round($times[(int) ($count * 0.99)] * 1000, 2) . 'ms',
            'rps' => round($count / $sum, 2),
        ];
    }
}

// 使用示例
$benchmark = new OpcacheBenchmark('http://localhost/api/test', 200);
$results = $benchmark->run();

echo "=== OPcache 性能基准测试 ===" . PHP_EOL;
foreach ($results as $metric => $value) {
    echo "  {$metric}: {$value}" . PHP_EOL;
}

Grafana 监控指标导出

php
<?php
declare(strict_types=1);

/**
 * Prometheus 格式的 OPcache 监控指标导出
 * 可集成到 Prometheus + Grafana 监控系统
 */
class OpcacheMetricsExporter
{
    /**
     * 导出 Prometheus 格式指标
     */
    public function export(): string
    {
        $status = opcache_get_status(false);
        if ($status === false) {
            return '# OPcache is disabled';
        }

        $stats = $status['opcache_statistics'];
        $mem = $status['memory_usage'];

        $metrics = [];

        // 缓存命中率
        $metrics[] = $this->gauge(
            'opcache_hit_rate_percent',
            (float) $stats['opcache_hit_rate']
        );

        // 内存指标
        $metrics[] = $this->gauge(
            'opcache_memory_used_bytes',
            $mem['used_memory']
        );
        $metrics[] = $this->gauge(
            'opcache_memory_free_bytes',
            $mem['free_memory']
        );
        $metrics[] = $this->gauge(
            'opcache_memory_wasted_bytes',
            $mem['wasted_memory'] ?? 0
        );

        // 缓存数量
        $metrics[] = $this->gauge(
            'opcache_num_cached_scripts',
            $stats['num_cached_scripts']
        );
        $metrics[] = $this->gauge(
            'opcache_num_cached_keys',
            $stats['num_cached_keys']
        );

        // 重启次数
        $metrics[] = $this->gauge(
            'opcache_oom_restarts_total',
            $stats['oom_restarts']
        );
        $metrics[] = $this->gauge(
            'opcache_hash_restarts_total',
            $stats['hash_restarts']
        );

        // 命中/未命中计数
        $metrics[] = $this->gauge(
            'opcache_hits_total',
            $stats['hits']
        );
        $metrics[] = $this->gauge(
            'opcache_misses_total',
            $stats['misses']
        );

        return implode("\n", $metrics) . "\n";
    }

    private function gauge(string $name, int|float $value): string
    {
        return "opcache_{$name} {$value}";
    }
}

// HTTP 端点导出
if (PHP_SAPI === 'cli' || (isset($_GET['metrics']) && $_SERVER['REMOTE_ADDR'] === '127.0.0.1')) {
    header('Content-Type: text/plain; version=0.0.4; charset=utf-8');
    $exporter = new OpcacheMetricsExporter();
    echo $exporter->export();
}

注意事项

常见陷阱

  1. 冷启动问题:PHP-FPM 重启后缓存为空,首次请求延迟高。通过文件缓存和预热脚本解决。

  2. validate_timestamps 的误解:在 opcache.enable_cli = 0 时,CLI 脚本中的 OPcache 操作不会影响 FPM 进程。

  3. opcache_invalidate 不会触发重新编译:该函数仅标记缓存为无效,下次请求时才会重新编译。

  4. 多 PHP-FPM pool 的内存隔离:每个 pool 有独立的 PHP 进程组,但共享同一块 OPcache 共享内存。

故障排查

php
<?php
declare(strict_types=1);

/**
 * OPcache 故障排查工具
 */
class OpcacheTroubleshooter
{
    public function run(): void
    {
        echo "=== OPcache 故障排查 ===" . PHP_EOL . PHP_EOL;

        // 1. 检查扩展是否加载
        $this->checkExtensionLoaded();

        // 2. 检查配置
        $this->checkConfiguration();

        // 3. 检查内存
        $this->checkMemory();

        // 4. 检查文件缓存
        $this->checkFileCache();

        // 5. 检查 JIT
        $this->checkJit();
    }

    private function checkExtensionLoaded(): void
    {
        echo "1. 扩展检查" . PHP_EOL;
        echo "   OPcache 已加载: " . (extension_loaded('Zend OPcache') ? '是' : '否') . PHP_EOL;
        echo "   SAPI: " . PHP_SAPI . PHP_EOL;
        echo "   PHP 版本: " . PHP_VERSION . PHP_EOL;
        echo PHP_EOL;
    }

    private function checkConfiguration(): void
    {
        echo "2. 配置检查" . PHP_EOL;

        $checks = [
            ['opcache.enable', '1', 'OPcache 必须启用'],
            ['opcache.save_comments', '1', '注释保存应启用'],
        ];

        foreach ($checks as [$key, $expected, $desc]) {
            $actual = ini_get($key);
            $status = ($actual === $expected) ? 'OK' : '检查';
            echo "   [{$status}] {$key} = {$actual} (期望: {$expected}) - {$desc}" . PHP_EOL;
        }

        // 环境相关检查
        if (PHP_SAPI === 'fpm') {
            $ts = ini_get('opcache.validate_timestamps');
            if ($ts === '1') {
                echo "   [WARN] validate_timestamps=1 在 FPM 中会增加开销" . PHP_EOL;
            }
        }

        echo PHP_EOL;
    }

    private function checkMemory(): void
    {
        echo "3. 内存检查" . PHP_EOL;
        $status = opcache_get_status(false);
        if ($status === false) {
            echo "   OPcache 未启用,跳过" . PHP_EOL;
            return;
        }

        $mem = $status['memory_usage'];
        $totalMB = ($mem['used_memory'] + $mem['free_memory']) / 1024 / 1024;
        $usedMB = $mem['used_memory'] / 1024 / 1024;
        $usedPercent = ($mem['used_memory'] / ($mem['used_memory'] + $mem['free_memory'])) * 100;

        echo "   总内存: " . round($totalMB, 2) . "MB" . PHP_EOL;
        echo "   已使用: " . round($usedMB, 2) . "MB ({$usedPercent}%)" . PHP_EOL;

        if (isset($mem['wasted_memory']) && $mem['wasted_memory'] > 0) {
            echo "   浪费: " . round($mem['wasted_memory'] / 1024 / 1024, 2) . "MB" . PHP_EOL;
        }

        $stats = $status['opcache_statistics'];
        if ($stats['oom_restarts'] > 0) {
            echo "   [CRITICAL] OOM 重启: {$stats['oom_restarts']} 次" . PHP_EOL;
        }

        echo PHP_EOL;
    }

    private function checkFileCache(): void
    {
        echo "4. 文件缓存检查" . PHP_EOL;
        $fileCache = ini_get('opcache.file_cache');
        echo "   文件缓存目录: " . ($fileCache ?: '未启用') . PHP_EOL;
        if ($fileCache && is_dir($fileCache)) {
            echo "   目录存在: 是" . PHP_EOL;
            echo "   目录可写: " . (is_writable($fileCache) ? '是' : '否') . PHP_EOL;
        }
        echo PHP_EOL;
    }

    private function checkJit(): void
    {
        echo "5. JIT 检查" . PHP_EOL;
        $jit = ini_get('opcache.jit');
        $jitBuffer = ini_get('opcache.jit_buffer_size');
        echo "   JIT 模式: {$jit}" . PHP_EOL;
        echo "   JIT 缓冲区: {$jitBuffer}" . PHP_EOL;
    }
}

if (PHP_SAPI === 'cli') {
    $troubleshooter = new OpcacheTroubleshooter();
    $troubleshooter->run();
}

最佳实践

1. 部署流程最佳实践

1. 代码合并到生产分支
2. 运行测试套件
3. 部署代码到服务器
4. 运行 opcache_compile_file 预热关键文件
5. 验证应用健康状态
6. 监控命中率回归

2. 监控集成

  • opcache_get_status() 数据接入 Prometheus/Grafana
  • 设置命中率告警阈值:低于 95% Warning,低于 90% Critical
  • 监控 oom_restartshash_restarts 计数器

3. 容量规划

  • 使用 opcache.file_cache 持久化缓存,加速重启
  • 共享内存至少预留 30% 余量
  • 在流量高峰前执行缓存预热

4. 安全考虑

  • 使用 opcache.restrict_api 限制管理功能调用路径
  • 管理接口添加 IP 白名单和 Token 认证
  • 定期审计 OPcache 配置

下一节

继续学习:JIT 编译器

参考链接