坑!坑!坑!opcache的坑我已经踩到腿残

吴思 360云计算

女主宣言

开发时我们经常会使用各种各样的组件,系统提供的、第三方集成的,但组件经常会给我们挖不大不小的坑儿,需要我们一个个地去踩,这里说说这些年我们正在踩的坑儿 —— 之opcache PS:丰富的一线技术、多元化的表现形式,尽在“HULK一线技术杂谈”,点关注哦!

发现问题

同事A在发布新版本到线上后,例行检查,却发现没有达到预期的效果; 查代码、查服务器,无果; 半晌之后,说让我给看看。 A做事比较细心,一听他这么说,我心想:不会掉坑儿里了吧?

于是接手:

从lvs里摘下一台服务器,本地绑定host; 重启nginx,现象依旧; 重启fpm,现象消失,嗯,这里了; 查看fpm配置,正常; 查看php配置,没有发现问题,哦,用opcache了,难道这里?

继续尝试:

禁用opcache,再发布一个版本,现象消失; 哦,opcache,妳肿么了?

鉴于opcache成熟应用了这么久,猜测这个问题应该是在特定的环境下才会触发; 特定什么的,查找起来就费事儿了,那就碰运气吧; 先stack trace,看stack能给个hint不? 启用opcache,再发布一个版本,打开项目的报错功能,故意访问一个不存在的controller,发现stack里出现了不同的版本号,wtf ? 不同版本号,你俩怎么共事起来了?你们之间的共同点,不就是link之前指向你、后来指向他么;咦,symlink?

内容截取:


exception 'QFrameRunException' with message'Controller File('/home/q/server/project_1002/src/application/controllers/1Controller.php')is Not Exist!!!
' in/home/q/php/QFrame/base/QFrameutils.php:114
Stack trace:
#0/home/q/php/QFrame/web/QFrameweb.php(168):QFrameBizResult::ensureNotFalse(false, 'Controller File...')
#1/home/q/php/QFrame/web/QFrameweb.php(150): QFrameWeb->dispatch()
#2/home/q/php/QFrame/web/QFrameweb.php(129): QFrameWeb->runController('1')
#3 /home/q/php/QFrame/web/QFrameweb.php(37):QFrameWeb->processRequest()
#4 /home/q/server/project_1001/src/www/index.php(13):QFrameWeb->run()
#5 {main}
 

求救:

我们的代码框架+服务器配置跟其他同事的环境类似,这个问题应该有同事遇到过,那么领导收集的信息应该是最全的,找领导! 一反馈,还真有同事遇到过,但没解决掉,领导怀疑是opcache+symlink的组合出了问题; google之,发现lerdorf(php创始人)在github上回复的这个问题,soga!

该填坑儿了!

---------------事件整理---------------

  • 环境 opcache+symlink

  • 现象 运行时发现不会使用新版本

  • 解决办法 nginx配置修改


fastcgi_paramSCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_paramDOCUMENT_ROOT $realpath_root;
  • 解析 opcache依赖的是opcode arrays,结构如下:

["/home/q/server/project_1001/src/www/index.php"]=>
array(6) {
        ["full_path"]=>
        string(72) "/home/q/server/project_1001/src/www/index.php"
        ["hits"]=>
        int(18)
        ["memory_consumption"]=>
        int(3976)
        ["last_used"]=>
        string(24) "Thu Nov 17 15:52:26 2016"
        ["last_used_timestamp"]=>
        int(1479369146)
        ["timestamp"]=>
        int(1478779049)
}

以不同版本的文件为例:


drwxr-xr-x 7 xxx xxx  4096 11月 10 19:43project_1001
drwxr-xr-x 7 xxx xxx  4096 11月 10 19:57project_1002
lrwxrwxrwx 1 xxx xxx    54 11月 18 10:06 project -> /home/q/server/project_1002

project先link到project_1001,opcache缓存index.php文件时,key=/home/q/server/project_1001/src/www/index.php,value是文件信息(见上述结构); project后link到project_1002,opcache利用fastcgi参数DOCUMENT_ROOT、SCRIPT_FILENAME来读取path时,发现path没有变化,导致不会解析到project_1002,即project_1002不会生效; 但如果在fastcgi配置为$realpath_root,就可以让path发生变化,来保证project_1002生效;

PS: 我是在php5.5环境尝试的,php的其他版本仅供参考; lerdorf指出了其他的解决方案,分明针对是path是relative path、web server是apache的情况,有兴趣的同学可以关注;

参考文档 https://github.com/zendtech/ZendOptimizerPlus/issues/126 over