PHP Opcache工作原理
PHP项目中,尤其是在高并发大流量的场景中,如何提升PHP的响应时间,是一项十分重要的工作。
而Opcache又是优化PHP性能不可缺失的组件,尤其是应用了PHP框架的项目中,作用更是明显。
1.概述
在理解OPCache功能之前,我们有必要先理解PHP-FPM+Nginx的工作机制,以及PHP脚本解释执行的机制。
1.1PHP-FPM+Nginx的工作机制
请求从Web浏览器到Nginx,再到PHP处理完成,一共要经历如下五个步骤:
第一步:启动服务
- 启动PHP-FPM。PHP-FPM支持两种通信模式:TCPsocket和Unixsocket;
- PHP-FPM会启动两种类型的进程:Master进程和Worker进程,前者负责监控端口、分配任务、管理Worker进程;后者就是PHP的cgi程序,负责解释编译执行PHP脚本。
- 启动Nginx。首先会载入ngx_http_fastcgi_module模块,初始化FastCGI执行环境,实现FastCGI协议请求代理
- 这里要注意:fastcgi的worker进程(cgi进程),是由PHP-FPM来管理,不是Nginx。Nginx只是代理
第二步:Request=>Nginx
- Nginx接收请求,并基于location配置,选择一个合适handler
- 这里就是代理PHP的handler
第三步:Nginx=>PHP-FPM
- Nginx把请求翻译成fastcgi请求
- 通过TCPsocket/UnixSocket发送给PHP-FPM的master进程
第四步:PHP-FPMMaster=>Worker
- PHP-FPMmaster进程接收到请求
- 分配Worker进程执行PHP脚本,如果没有空闲的Worker,返回502错误
- Worker(php-cgi)进程执行PHP脚本,如果超时,返回504错误
- 处理结束,返回结果
第五步:PHP-FPMWorker=>Master=>Nginx
- PHP-FPMWorker进程返回处理结果,并关闭连接,等待下一个请求
- PHP-FPMMaster进程通过Socket返回处理结果
- NginxHandler顺序将每一个响应buffer发送给第一个filter→第二个→以此类推→最终响应发送给客户端
1.2PHP脚本解释执行的机制
了解了PHP+Nginx整体的处理流程后,我们接下来看一下PHP脚本具体执行流程,
首先我们看一个实例:
我们分析一下执行过程:
- php初始化执行环节,启动Zend引擎,加载注册的扩展模块
- 初始化后读取脚本文件,Zend引擎对脚本文件进行词法分析(lex),语法分析(bison),生成语法树
- Zend引擎编译语法树,生成opcode,
- Zend引擎执行opcode,返回执行结果
在PHPcli模式下,每次执行PHP脚本,四个步骤都会依次执行一遍;
在PHP-FPM模式下,步骤1)在PHP-FPM启动时执行一次,后续的请求中不再执行;步骤2)~4)每个请求都要执行一遍;
其实步骤2)、3)生成的语法树和opcode,同一个PHP脚本每次运行的结果都是一样的,
在PHP-FPM模式下,每次请求都要处理一遍,是对系统资源极大的浪费,那么有没有办法优化呢?
当然有,如:
- OPCache:前身是ZendOptimizer+,是ZendServer的一个开源组件;官方出品,强力推荐
- APC:AlternativePHPCache是一个开放自由的PHPopcode缓存组件,用于缓存、优化PHP中间代码;已经不更新了不推荐
- APCu:是APC的一个分支,共享内存,缓存用户数据,不能缓存opcode,可以配合Opcache使用
- eAccelerate:同样是不更新了,不推荐
- xCache:不再推荐使用了
2.OPCache介绍
OPCache是Zend官方出品的,开放自由的opcode缓存扩展,还具有代码优化功能,省去了每次加载和解析PHP脚本的开销。
PHP5.5.0及后续版本中已经绑定了OPcache扩展。
缓存两类内容:
- OPCode
- InternedString,如注释、变量名等
3.OPCache原理
OPCache缓存的机制主要是:将编译好的操作码放入共享内存,提供给其他进程访问。
这里就涉及到内存共享机制,另外所有内存资源操作都有锁的问题,我们一一解读。
3.1共享内存
UNIX/Linux系统提供很多种进程间内存共享的方式:
- System-VshmAPI:SystemV共享内存,
- sysvshm是持久化的,除非被一个进程明确的删除,否则它始终存在于内存里,直到系统关机;
- mmapAPI:
- mmap映射的内存在不是持久化的,如果进程关闭,映射随即失效,除非事先已经映射到了一个文件上
- 内存映射机制mmap是POSIX标准的系统调用,有匿名映射和文件映射两种
- mmap的一大优点是把文件映射到进程的地址空间
- 避免了数据从用户缓冲区到内核pagecache缓冲区的复制过程;
- 当然还有一个优点就是不需要频繁的read/write系统调用
- POSIXAPI:SystemV的共享内存是过时的,POSIX共享内存提供了使用更简单、设计更合理的API.
- UnixsocketAPI
OPCache使用了前三个共享内存机制,根据配置或者默认mmap内存共享模式。
依据PHP字节码缓存的场景,OPCache的内存管理设计非常简单,快速读写,不释放内存,过期数据置为Wasted。
当Wasted内存大于设定值时,自动重启OPCache机制,清空并重新生成缓存。
3.2互斥锁
任何内存资源的操作,都涉及到锁的机制。
共享内存:一个单位时间内,只允许一个进程执行写操作,允许多个进程执行读操作;
写操作同时,不阻止读操作,以至于很少有锁死的情况。
这就引发另外一个问题:新代码、大流量场景,进程排队执行缓存opcode操作;重复写入,导致资源浪费。
4.OPCache缓存解读
OPCache是官方的Opcode缓存解决方案,在PHP5.5版本之后,已经打包到PHP源码中一起发布。
它将PHP编译产生的字节码以及数据缓存到共享内存中,在每次请求,从缓存中直接读取编译后的opcode,进行执行。
通过节省脚本的编译过程,提高PHP的运行效率。
如果正在使用APC扩展,做同样的工作,现在强烈推荐OPCache来代替,尤其是PHP7中。
4.1OPCode缓存
Opcache会缓存OPCode以及如下内容:
- PHP脚本涉及到的函数
- PHP脚本中定义的Class
- PHP脚本文件路径
- PHP脚本OPArray
- PHP脚本自身结构/内容
4.2InternedString缓存
首先我们需要理解,什么是InternedString?
在PHP5.4的时候,引入了InternedString机制,用于优化PHP对字符串的存储和处理。
尤其是处理大块的字符串,比如PHPdoces时,InternedString可以优化内存。
InternedString缓存的内容包括:变量名称、类名、方法名、字符串、注释等。
在PHP-FPM模式中,InternedString缓存字符,仅限于Worker进程内部。
而缓存到OPCache中,那么Worker进程之间可以使用InternedString缓存的字符串,节省内存。
我们需要注意一个事情,在PHP开发中,一般会有大段的注释,也会被缓存到OPCache中。
可以通过php.ini的配置,关闭注释的缓存。
但是,像ZendFramework等框架中,会引用注释,所以,是否关闭注释的缓存,需要区别对待。
5.OPCache更新策略
是缓存,都存在过期,以及更新策略等。
而OPCache的更新策略非常简单,到期数据置为Wasted,达到设定值,清空缓存,重建缓存。
这里需要注意:在高流量的场景下,重建缓存是一件非常耗费资源的事儿。
OPCache在创建缓存时并不会阻止其他进程读取。
这会导致大量进程反复新建缓存。所以,不要设置OPCache过期时间
每次发布新代码时,都会出现反复新建缓存的情况。如何避免呢?
- 不要在高峰期发布代码,这是任何情况下都要遵守的规则
- 代码预热,比如使用脚本批量调PHP访问URL,或者使用OPCache暴露的API如opcache_compile_file()进行编译缓存
6.OPCache的配置
6.1内存配置
- opcache.preferred_memory_model="mmap"OPcache首选的内存模块。如果留空,OPcache会选择适用的模块,通常情况下,自动选择就可以满足需求。可选值包括:mmap,shm,posix以及win32。
- opcache.memory_consumption=64OPcache的共享内存大小,以兆字节为单位,默认64M
- opcache.interned_strings_buffer=4用来存储临时字符串的内存大小,以兆字节为单位,默认4M
- opcache.max_wasted_percentage=5浪费内存的上限,以百分比计。如果达到此上限,那么OPcache将产生重新启动续发事件。默认5
6.2允许缓存的文件数量以及大小
- opcache.max_accelerated_files=2000OPcache哈希表中可存储的脚本文件数量上限。真实的取值是在质数集合{223,463,983,1979,3907,7963,16229,32531,65407,130987}中找到的第一个大于等于设置值的质数。设置值取值范围最小值是200,最大值在PHP5.5.6之前是100000,PHP5.5.6及之后是1000000。默认值2000
- opcache.max_file_size=0以字节为单位的缓存的文件大小上限。设置为0表示缓存全部文件。默认值0
6.3注释相关的缓存
- opcache.load_commentsboolean如果禁用,则即使文件中包含注释,也不会加载这些注释内容。本选项可以和opcache.save_comments一起使用,以实现按需加载注释内容。
- opcache.fast_shutdownboolean如果启用,则会使用快速停止续发事件。所谓快速停止续发事件是指依赖Zend引擎的内存管理模块一次释放全部请求变量的内存,而不是依次释放每一个已分配的内存块。
6.4二级缓存的配置
- opcache.file_cache配置二级缓存目录并启用二级缓存。启用二级缓存可以在SHM内存满了、服务器重启或者重置SHM的时候提高性能。默认值为空字符串"",表示禁用基于文件的缓存。
- opcache.file_cache_onlyboolean启用或禁用在共享内存中的opcode缓存。
- opcache.file_cache_consistency_checksboolean当从文件缓存中加载脚本的时候,是否对文件的校验和进行验证。
- opcache.file_cache_fallbackboolean在Windows平台上,当一个进程无法附加到共享内存的时候,使用基于文件的缓存,也即:opcache.file_cache_only=1。需要显示的启用文件缓存。
原文地址:
- 【PHP高级】深入探索Opcache工作原理