3

fpm模式下读取到is_cli为何为true - H&K

 1 year ago
source link: https://www.cnblogs.com/HKUI/p/opcache-is-cli-true.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.




php-fpm下读取到is_cli为true,不知道你们是否遇到过,我是遇到了。。。。
有人会说,即使为true又怎么了,你是没遇到有些根据is_cli来走不同逻辑判断的,如果读取的是错的就会引起很大的问题。。。。

问题出现和简单排查

维护的老系统里有个上传的服务,用的是比较老的codeigniter,构建完代码后,突然发现 1个上传url报路径找不到
具体表现如下

WEB7c058dd33e332841e3284e8e073a2be4?method=download&shareKey=c1b7aa4514e99c6c5d3129fdaa2d4931

因为这里是a1.domain.com 去调取upload.domain.com,所以出现跨域(如果upload.domain.com 正常的话,是有设置跨域的),现在明显设置跨域的失效了

直接打开链接看,如下图

WEB8f0494bd6139a8fb3d17befd5921d4c2?method=download&shareKey=c37a4bd3885aa683c0b7bf0878de9175

因为是线上,即使再自信没改到这里,也要赶紧联系运维同事回滚代码,但是回滚后发现依然如此。
当时急的不行,让测试同事让他看看其它的上传链接是否可正常上传,发现其它的上传(比如视频上传,其它的图片的上传)是没问题的,唯一的区别就是走不走这个index.php入口文件

因为当时已经晚上近10点了,使用的人也不多,一边让测试同学帮验证。我这边赶紧查代码。日常开发用的不是CI框架,赶紧搜索

ERROR: Not Found The controller/method pair you requested was not found.

这个是哪提示出来的,
在项目中发现代码位置如下,而且仅此一处

WEBff0f90ad234055e22256164d2ca71899?method=download&shareKey=77ed221f7451c21c809a6f89db4cff56

而且看到前面的is_cli,就是纳闷我这是php-fpm的网页请求,为何is_cli为true呢

追到is_cli的实现

if ( ! function_exists('is_cli'))
{

	/**
	 * Is CLI?
	 *
	 * Test to see if a request was made from the command line.
	 *
	 * @return 	bool
	 */
	function is_cli()
	{
		return (PHP_SAPI === 'cli' OR defined('STDIN'));
	}
}

后来一路追到ci的路由解析

system/core/Router.php

WEBfddcd661d22c9ac6ae8c428e80a9556c?method=download&shareKey=9ea873de8c4c5be5f546facfbd5dc3d6
124         public function __construct($routing = NULL)
125         {
126                 $this->config =& load_class('Config', 'core');
127                 $this->uri =& load_class('URI', 'core');
128                 //var_dump(PHP_SAPI);
129                 //var_dump(defined('STDIN'));
130                 //var_dump( is_cli());
131                 $this->enable_query_strings = ( ! is_cli() && $this->config->item('enable_query_strings') === TRUE);
132
133                 // If a directory override is configured, it has to be set before any dynamic routing logic
134                 is_array($routing) && isset($routing['directory']) && $this->set_directory($routing['directory']);
135                 $this->_set_routing();
136
137                 // Set any routing overrides that may exist in the main index file
138                 if (is_array($routing))
139                 {
140                         empty($routing['controller']) OR $this->set_class($routing['controller']);
141                         empty($routing['function'])   OR $this->set_method($routing['function']);
142                 }
143
144                 log_message('info', 'Router Class Initialized');
145         }

结合上图128,129行和上面is_cli函数的实现代码,130行不可能为true啊

脑袋快要炸了,通过调试发现只要131行的$this->enable_query_strings为true,那么上传功能就没问题

经过思考和猜测,严重怀疑是fpm读取到了cli下的opcache

主要基于以下几点

  • 其它入口(非index.php)的路径没问题
  • 命令行里有php index.php 这种定时脚本在跑
  • opcache的配置
ri了一下如下
$ php --ri 'Zend opcache'

Zend OPcache

Opcode Caching => Up and Running
Optimization => Enabled
SHM Cache => Enabled
File Cache => Enabled
Startup => OK
Shared memory model => mmap
Cache hits => 0
Cache misses => 0
Used memory => 36560720
Free memory => 231874736
Wasted memory => 0
Interned Strings Used memory => 415960
Interned Strings Free memory => 16361256
Cached scripts => 0
Cached keys => 0
Max keys => 16229
OOM restarts => 0
Hash keys restarts => 0
Manual restarts => 0

Directive => Local Value => Master Value
opcache.enable => On => On
opcache.use_cwd => On => On
opcache.validate_timestamps => On => On
opcache.validate_permission => Off => Off
opcache.validate_root => Off => Off
opcache.inherited_hack => On => On
opcache.dups_fix => Off => Off
opcache.revalidate_path => Off => Off
opcache.log_verbosity_level => 1 => 1
opcache.memory_consumption => 256 => 256
opcache.interned_strings_buffer => 16 => 16
opcache.max_accelerated_files => 8000 => 8000
opcache.max_wasted_percentage => 10 => 10
opcache.consistency_checks => 0 => 0
opcache.force_restart_timeout => 3600 => 3600
opcache.revalidate_freq => 2 => 2
opcache.file_update_protection => 2 => 2
opcache.preferred_memory_model => no value => no value
opcache.blacklist_filename => no value => no value
opcache.max_file_size => 0 => 0
opcache.protect_memory => 0 => 0
opcache.save_comments => 1 => 1
opcache.fast_shutdown => 0 => 0
opcache.optimization_level => 0x7FFFBFFF => 0x7FFFBFFF
opcache.opt_debug_level => 0 => 0
opcache.enable_file_override => Off => Off
opcache.enable_cli => On => On
opcache.error_log => no value => no value
opcache.restrict_api => no value => no value
opcache.lockfile_path => /tmp => /tmp
opcache.file_cache => /tmp => /tmp
opcache.file_cache_only => 0 => 0
opcache.file_cache_consistency_checks => 1 => 1
opcache.huge_code_pages => Off => Off

这里有下面几个配置项对fpm下读取到cli的缓存有关

zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=8000
opcache.max_wasted_percentage=10
opcache.use_cwd=1
opcache.force_restart_timeout=3600
opcache.file_cache=/tmp
  • 1.开启了cli的opcache 即(enable_cli=1)
  • 2.使用了二级文件缓存 即(opcache.file_cache=/tmp)

于是尝试删除opcache的文件缓存,然后重启fpm,就好了

(实际上是,我打日志调着调着 突然自己好了,看fpm的日志是fpm触发了自动重启,我打日志时有修改了相关文件,fpm重启时检查文件更新重新生成了opcache)

后来为了防止这种情况再次发生就关闭了cli下的opcache,删除opcache文件缓存,重启fpm

然后我在测试上不断复现,发现可以稳定复现,实锤是fpm下读取到了cli已经生成好的缓存了

这次的问题,我归结为以下两点

  • 对opcache的机制认识不够
  • CI框架这种fpm里和cli用了同样的入口文件而且根据is_cli来进行路由解析,会在我上面的配置和使用下出问题

现在有以下代码

路径为/data/www/emlog/op/

test.php
include/fun.php
invalidate

test.php

include "include/fun.php";
var_dump(sapi());
var_dump(is_cli());

include/fun.php

function sapi(){
	return php_sapi_name();
}
function is_cli()
 {
         return (PHP_SAPI === 'cli' OR defined('STDIN'));
 }

invalidate.php

$files=[
	'/data/www/emlog/op/test.php',
	'/data/www/emlog/op/include/fun.php',
];
foreach($files as $f){
    $r=opcache_invalidate($f,true);
    var_dump($r);
}

opcache配置

[opcache]
zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache=/tmp

opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=8000
opcache.max_wasted_percentage=10
opcache.use_cwd=1
opcache.force_restart_timeout=3600
opcache.validate_timestamps=1
opcache.revalidate_freq=2
opcache.revalidate_path=0

主要是前4个的配置
按照下图操作

WEB577d8ee7ff938a58a6c964cbdfe88472?method=download&shareKey=73e239302ef6539679869c3175972910

更清楚的图片见 https://note.youdao.com/ynoteshare/index.html?id=2275a62e0fa926f2cf576940a1cd93d4&type=note&_time=1679154215415

is_cli为true时的缓存

[root@hkui-qy tmp]# cat 8fc9c56d14b6542c6ff7147207730f6b/data/www/emlog/op/include/fun.php.bin |strings
OPCACHE
8fc9c56d14b6542c6ff7147207730f6b0
%%1n
include/fun.php:235496:235544:/data/www/emlog/op
/data/www/emlog/op/include/fun.php
is_cli
sapi
php_sapi_name

is_cli为false时的缓存

[root@hkui-qy tmp]# cat 8fc9c56d14b6542c6ff7147207730f6b/data/www/emlog/op/include/fun.php.bin |strings
OPCACHE
8fc9c56d14b6542c6ff7147207730f6b`
include/fun.php:235648:235696:/data/www/emlog/op
/data/www/emlog/op/include/fun.php
496:
is_cli
STDIN
stdin
sapi
php_sapi_name

共享内存缓存与文件缓存

  • fpm在启动或者重启时
    • 如果发现代码文件和缓存文件匹配,那么会读取文件的缓存到共享内存,所以使用文件缓存(可提前用opcache_compile_file生成opcache),在fpm重启时,能更快的获取opcache,减少内存使用
    • 如果发现代码文件和缓存文件对不匹配(缓存不存在或者代码文件有改变),那么会重新生成缓存,并同步到文件缓存里
  • 文件修改,fpm检测到了文件的变化,会重新生成共享内存缓存,并不会立马更新到文件缓存里,fpm重启 然后重新生成缓存后才会更新到文件缓存

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK