PHP 的 realpath cache 的应用

980 阅读3分钟

一、 realpath cache 介绍

        realpath 是 PHP 中的一个重要概念,尤其在使用软链接的方式部署代码的时候

        当我们在操作系统中访问文件的时候,系统内核和文件系统需要知道我们访问的文件的具体信息,此时内核和文件系统需要解析文件信息(是文件还是目录,软链接需要解析到真实文件,相对路径则需要解析出绝对路径)。

        在 PHP 项目中,尤其使用框架的项目,有大量文件需要访问。访问这些文件,则需要通过内核解析这些文件的信息,最终将这些文件信息缓存到 realpath cache 中。需要指出的是,realpath cache 是进程隔离的,每个 php-fpm 的 worker 进程都有自己的 realpath cache。

        PHP 中关于 realpath cache 的配置有两项:

                realpath_cache_size        realpath 缓存所占用的最大空间

                realpath_cache_ttl            realpath 缓存的有效期

二、realpath cache 在项目部署中的应用

        通常情况下,我们在生产环境部署代码会创建一个软链接,然后将软链接指向实际的项目路径。当对项目进行升级部署时,常规的操作方式是先上传项目代码,然后删除原有的软链接,最后建立新的软链接指向新上传的项目路径。

        此时,当有新的请求进来时,如果 realpath cache 中的内容还没有过期(过期与否取决于 realpath_cache_ttl 的值),则请求会访问到缓存中的旧文件,新的代码则不会被访问。

        假如旧的项目路径为 /var/www/project/old,相应的软链接的路径为 /home/www/project/,请求访问的文件为 index.php。Nginx 将 /home/www/project/index.php 传给 php-fpm,php-fpm 解析得到真实的文件路径 /var/www/project/old/index.php,将解析得到的结果缓存到 realpath cache 中,并响应请求。此时如果我们更新项目,项目路径为 /var/www/project/new,并将 /home/www/project 指向 /var/www/project/new。此时,如果再次请求 index.php,Nginx 传给 php-fpm 的文件路径依然是 /home/www/project/index.php,而此时 php-fpm 的 realpath cache 中已经缓存了相应的文件信息,于是 /var/www/project/old/index.php 会被执行。

        为了避免这种情况的发生,Nginx 传给 php-fpm 的应该是文件的真实路径,而不应该是软链接路径。要实现这个效果,需要对 Nginx 的相关配置文件做一些修改。

        首先,将项目的 Nginx 配置文件中 PHP 配置部分的

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        改为

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;

        然后在项目的 Nginx 配置文件中 PHP 配置部分最后添加如下配置

fastcgi_param DOCUMENT_ROOT $realpath_root;


        另外,在部署项目切换软链接实际指向的地址时,为了保证原子性,应该先创建一个新的软链接指向新项目的路径,然后用新的软链接覆盖旧的软链接,如下:

ln -s new project_tmp && mv -Tf  project_tmp  project

三、实测

        两个完全相同的 Laravel 项目:Project-A、Project-B,项目软链接目录 laravel,项目测试代码:

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

// 用于测试 document_root 改为 realpath_root 后的效果
Route::get('/', function () {
    return view('welcome');
});

/*  
 * 用于测试 php-fpm 中 realpath cache 内容的变化
 * 由于 realpath cache 是进程隔离的,所以在测试前将 php-fpm worker 进程数量调整为 1 个
*/
Route::get('/list', function () {
    print_r(getmypid()); // 取得响应每次请求的 php-fpm worker 进程的 pid,保证每次请求始终由同一个 worker 进程响应
    print_r(PHP_EOL);
    print_r(realpath_cache_size()); // 取得 realpath cache 实际消耗的空间,作为调整 realpath_cache_size  配置的参照
    /*
        以下代码是笔者的最后测试版本,起初在每次请求时先将 realpath cache 中文件路径写入 log 文件,
        观察随着 realpath_cache_size 配置的变化,缓存内容的变化
        以下代码测试的是软链接的实际目录切换后,realpath cache 中缓存的文件的变化
    */
    $log = storage_path('app/log');
    $arr = file($log);
    $arr = array_map(function ($v) {
        return trim($v);
    }, $arr);
    $keys = array_keys(realpath_cache_get());
    Storage::put('log', join(PHP_EOL, $keys));
    return dd(array_diff($keys, $arr));
});

        测试结果表明,Nginx 项目配置文件中的 document_root 改为 realpath_root 后,相同的 request 新项目的代码会被立刻执行。而 realpath cache 中的内容则取决于 realpath_cache_size 的配置,如果 realpath_cache_size 的值设置的足够大,则切换到新项目后,老项目的缓存依然存在;如果减小 realpath_cache_size 的值,则缓存空间有限的情况下,老项目的缓存会优先被清除。