一、 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 的值,则缓存空间有限的情况下,老项目的缓存会优先被清除。