PHP 开启 Opcache 后的一些坑

PHP编程技术 网络投稿 2022-02-07 04:33 1257 0

众所周知,PHP5.5 以后官方自带了一个组件叫 Zend Opcache,具体可以参看官方文档。开启此扩展后可以提高 PHP 的执行效率,具体提升多大我会在最后给一个简简单单的测试,提供给大家参考。

Opcache 的执行流程大致如下,

E390.png

Opcache 的目地是避免重复编译,减少 CPU 和内存开销。

背景

OPcache通过opcode的缓存和优化,提供更快的PHP执行过程。
业务在php7环境运营时,为了提升请求的性能,在PHP7环境中配置OPcache扩展。
业务在更新代码后,访问业务系统时提示无法找到对应的文件或请求的内容还是更新前的旧内容,
webserver重启以后,请求访问到的文件就都是最新的了,问题就貌似解决了。

 

问题分析

根据现象分析,代码更新后请求找不到新增的文件,尤其是还在请求已有文件更新前的内容,那么可能跟缓存有关系,考虑到跟业务代码逻辑无关,关闭opcache的配置问题就不再出现,基本上可以定位到问题出在opcache的配置上。

 

技术分享

 

技术分享

cat /usr/local/php/etc/subconfig/opcache.inizend_extension=opcache.soopcache.enable=1opcache.revalidate_freq=0opcache.validate_timestamps=0opcache.max_accelerated_files=7963opcache.memory_consumption=192opcache.interned_strings_buffer=16opcache.fast_shutdown=1opcache.enable_cli=1


opcache.enable 启用操作码缓存,默认为“1”

如果禁用此选项,则不会优化和缓存代码。 在运行期使用 ini_set() 函数只能禁用 opcache.enable 设置,不可以启用此设置。 如果在脚本中尝试启用此设置项会产生警告。

opcache.enable_cli  仅针对 CLI 版本的 PHP 启用操作码缓存。

 通常被用来测试和调试。

opcache.revalidate_freq=0  检查脚本时间戳是否有更新的周期,以秒为单位。

设置为 0 会导致针对每个请求, OPcache 都会检查脚本更新。


opcache.validate_timestamps=0  如果启用,那么 OPcache 会每隔 opcache.revalidate_freq 设定的秒数 检查脚本是否更新。

如果禁用此选项,你必须使用 opcache_reset() 或者 opcache_invalidate() 函数来手动重置 OPcache,也可以 通过重启 Web 服务器来使文件系统更改生效。

 

最初的配置是:
opcache.revalidate_freq=60,opcache.validate_timestamps=1
即每60秒检测一次更新字节码缓存,业务代码更新后可能需要60秒以后才能访问到最新的内容,也就出现了最初访问不到新增的内容。

 

代码更新方式

php代码的更新方式有两种,一种是覆盖webserver配置的目录下的文件来更新,一种是每次都部署一个全量包目录,然后软链接到webserver指定的目录。

第一种覆盖更新的方式,如果使用在过期时间后自动清理opcache缓存内容的话,更新操作如果有延迟,就会出现新旧代码文件混合在一起的情况。

第二种全量包目录发布后,软链接到webserver指定路径的方式,虽然不会存在新旧文件混合的问题,但是在未自动清理时,即便webserver已经链接到webserver对应目录,业务访问的还是旧文件。

 

代码缓存的问题

目前使用rsync同步目录文件的方式是我们更新代码的主要方式,最初使用每60s定时清理opcache的缓存文件,在60s内更新的文件不会生效,就导致了业务反馈代码更新后访问不到的问题。

使用定时更新代码缓存的问题,还有更新文件较多时,代码文件发布的过程中缓存发生更新,将会有60s新旧文件的缓存混合存在的问题。

根据相关研究人员推荐,如果采用覆盖更新代码文件时,更新操作完毕后,手动清理缓存比较合适。

opcache.validate_timestamps=0

 即,将oopcache.validate_timestamps设置为0。


配置了opcache.validate_timestamps值为0,必须手动清空Zend OPcache缓存的字节码,才能访问到最新的文件内容。适合在生产环境中设置为0,但在开发环境会带来不便,可以在开发环境中这样配置启用自动验证缓存功能:

opcache.validate_timestamps=1
opcache.revalidate_freq=0

手动清理缓存

除了重启php-fpm的进程可以清理opcache缓存外,
手动清理缓存涉及到的opcache函数主要为:opcache_reset()和opcache_invalidate() 。

  1.  
    boolean opcache_reset ( void )
  2.  
    该函数将重置整个字节码缓存。
    在调用 opcache_reset() 之后,所有的脚本将会重新载入并且在下次被点击的时候重新解析。

 

需要注意的是,当PHP以PHP-FPM的方式运行的时候,opcache的缓存是无法通过php命令进行清除的,只能通过http或cgi到php-fpm进程的方式来清除缓存。

In some (most?) systems,
PHP‘s CLI has a separate opcode cache to the one used by the web server ,or PHP-FPM process,which means running opcache_reset() in the CLI,
won‘t reset the webserver/fpm opcode cache, and vice-versa.

 

曲线救国,使用命令行清理php-fpm的opcache缓存:

#!/bin/bashcgi-fcgi -v  > /dev/null 2>&1|| yum --enablerepo=epel install fcgi -y  > /dev/null 2>&1echo ‘<?php opcache_reset(); echo "ok\n";‘ > /tmp/php-fpm-opcache-reset.php;
SCRIPT_FILENAME=/tmp/php-fpm-opcache-reset.php REQUEST_METHOD=GET cgi-fcgi -bind -connect 127.0.0.1:9000;
rm -f /tmp/php-fpm-opcache-reset.php;

 


opcache_invalidate  废除指定脚本缓存

boolean opcache_invalidate ( string $script [, boolean $force = FALSE ] )
该函数的作用是使得指定脚本的字节码缓存失效。 如果 force 没有设置或者传入的是 FALSE,那么只有当脚本的修改时间 比对应字节码的时间更新,脚本的缓存才会失效。

参数 

script
缓存需要被作废对应的脚本路径

force
如果该参数设置为TRUE,那么不管是否必要,该脚本的缓存都将被废除。

 

opcache_invalidate可以针对单个或几个脚本进行来清理缓存。

总结

 

如果代码发布是全量发布,切换软链接的方式,可以设置opcache.validate_timestamps=1和opcache.validate_timestamps=1来定时自动更新缓存。

如果代码发布是覆盖更新旧目录,则可以重启php-fpm及在脚本中或代码文件中使用opcache_reset函数来清理所有缓存。

如果可以获取到更新的代码文件列表,则可以使用opcache_invalidate函数来清理代码,同时也可以避免影响到其他业务的缓存。