草庐IT

fpm模式下读取到is_cli为何为true

梦想远航 2023-03-28 原文


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

问题出现和简单排查

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

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

直接打开链接看,如下图

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

排查

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

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

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

而且看到前面的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

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个的配置
按照下图操作

更清楚的图片见 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重启 然后重新生成缓存后才会更新到文件缓存

有关fpm模式下读取到is_cli为何为true的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  3. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  4. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  5. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  6. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  7. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  8. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

  9. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  10. ruby - 导轨 4 : column reference "updated_at" is ambiguous with Postgres - 2

    我正在尝试使用“updated_at”字段的日期时间范围查询数据库。前端在JSON数组中发送查询:["2015-09-0100:00:00","2015-10-0223:00:00"]在RailsController中,我使用以下方法将两个字符串解析为DateTime:start_date=DateTime.parse(params[:date_range_arr][0])end_date=DateTime.parse(params[:date_range_arr][1])#...@events=@events.where('updated_atBETWEEN?AND?,start_d

随机推荐