PHP开启 OPCache 来降低负载并提高抗并发能力
opcache 其实就是 OpCode + cache,php 是一种解释型语言,它的执行可分为以下几个流程
这样一来的话,对于同一个文件,反复请求,就要不断解析、编译和执行PHP脚本,消耗过多资源。为了解决这个问题,OpCode 缓存技术应运而生
Opcode 缓存就是将编译好的Opcode码缓存在内存中,下次再执行这个脚本时,直接会去找到缓存下的opcode码,直接执行最后一步,而不再需要中间的步骤了,其目地是避免重复编译,减少 CPU 和内存开销
OPcache 参数说明
一样直接看官方文档: OPcache 配置
这边挑几个平时配置用的比较多的说明一下:
1. opcache.enable
- 默认值: 1
- 说明: 启用操作码缓存。如果禁用此选项,则不会优化和缓存代码。 在运行期使用 ini_set() 函数只能禁用 opcache.enable 设置,不可以启用此设置。 如果在脚本中尝试启用此设置项会产生警告。新的 php 版本是默认开启的,但是旧的比如
5.6.x
默认是关闭的
2. opcache.enable_cli
3. opcache.memory_consumption
- 默认值: 128
- 说明: OPcache 的共享内存大小,以兆字节为单位。最小允许值为 "8"。如果设置的值小于最小值,则强制设置为允许的最小值。
4. opcache.interned_strings_buffer
- 默认值: 8
- 说明: 存储预留字符串的内存大小。PHP 解释器在背后会找到相同字符串的多个实例,把这个字符串保存在内存中,如果再次使用相同的字符串,PHP 解释器会使用指针。这么做能节省内存。默认情况下,PHP 驻留的字符串会隔离在各个 PHP 进程中。这个设置能让 PHP-FPM 进程池中的所有进程把驻留字符串存储到共享的缓冲区中,以便在 PHP-FPM 进程池中的多个进程之间引用驻留字符串。
5. opcache.max_accelerated_files
- 默认值: 10000
- 说明: OPcache 哈希表中可存储的脚本文件数量上限。真实的取值是在质数集合{ 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987, 262237, 524521, 1048793 } 中找到的第一个大于等于设置值的质数。设置值取值范围最小值是 200,最大值是 1000000。超出范围的值将限制为允许的值。
6. opcache.validate_timestamps
- 默认值: 1
- 说明: 如果启用,那么 OPcache 会每隔
opcache.revalidate_freq
设定的秒数 检查脚本是否更新。 如果禁用此选项,你必须使用opcache_reset()
或者opcache_invalidate()
函数来手动重置 OPcache,也可以 通过重启 Web 服务器来使文件系统更改生效。
7. opcache.revalidate_freq
- 默认值: 2
- 说明: 检查脚本时间戳是否有更新的周期,以秒为单位。 设置为 0 会导致针对每个请求, OPcache 都会检查脚本更新。如果
opcache.validate_timestamps
配置指令设置为禁用,那么此设置项将会被忽略
8. opcache.fast_shutdown
- 默认值: 0
- 说明: 如果启用,则会使用快速停止续发事件。 所谓快速停止续发事件是指依赖 Zend 引擎的内存管理模块 一次释放全部请求变量的内存,而不是依次释放每一个已分配的内存块。从 PHP 7.2.0 开始, 默认执行
OPcache 相关配套函数
PHP 官方提供了与 OPcache 相关的函数,具体文档: OPcache 函数
函数名 | 说明 |
---|---|
opcache_compile_file | 该函数可以用于在不用运行某个 PHP 脚本的情况下,编译该 PHP 脚本并将其添加到字节码缓存中去。 该函数可用于在 Web 服务器重启之后初始化缓存,以供后续请求调用。 |
opcache_get_configuration | 该函数将返回缓存实例的配置信息。 |
opcache_get_status | 该函数将返回内存中缓存实例的状态信息。不会返回有关文件缓存的任何信息。 |
opcache_invalidate | 废除脚本缓存 |
opcache_is_script_cached | 该函数用于检测 PHP 脚本是否已经被缓存 |
opcache_reset | 该函数将重置整个字节码缓存。在调用 opcache_reset() 之后,所有的脚本将会重新载入并且在下次命中的时候重新解析。此函数仅重置内存中的缓存,不会重置文件缓存。 |
利用这些函数,我们可以手动清空 OPcache,或者也可以将其封装成脚本,在代码更新后自动执行脚本
配置
好了,现在有了大概了解后,我们就可以直接在php.ini中配置啦
[opcache]
zend_extension=opcache.so
opcache.enable = 1
opcache.memory_consumption=1024
opcache.interned_strings_buffer=32
opcache.max_accelerated_files=80000
opcache.revalidate_freq=30
opcache.fast_shutdown=1
opcache.enable_cli=1
注意,这边有几个参数是有调整的,比如将缓存的内存设置为 1024M,将存储预留字符串的内存大小设置为 32M,将 可存储的脚本文件数量上限设置为 80000,最后将缓存刷新的时间设置为 30s。最后重启一下 php-fpm 即可
安装状态面板
我们只需要将其下载到服务器的某一个目录下
cd /data/wwwroot/
git clone https://github.com/rlerdorf/opcache-status.git --depth=1
cd opcache-status
然后在 nginx 那边增加一个路由,然后转发解析到这个目录下的 opcache.php 就行了,注意最好只能内网才能访问
location ^~ /opcache {
root /data/wwwroot/opcache-status/;
try_files $uri /opcache.php;
fastcgi_pass 127.0.0.1:9000;
include fastcgi.conf;
allow 127.0.0.1;
allow xxx;
allow xxx;
deny all;
}
本地直接命令行运行php opcache.php
效果
OPcache 缓存刷新/重置的几种方式
- php 执行
opcache_reset()
方法,重置整个 Opcode 缓存,所有的PHP脚本将会被重新解析再预编译为 Opcode - php 执行
opcache_invalidate()
方法, 清除指定脚本缓存,可以传递两个参数,一个是刷新文件路径,一个是force字段, 如果 force 没有设置或者传入的是 FALSE,那么只有当脚本的修改时间 比对应Opcode的时间更新时,脚本的缓存才会失效 - 重启 php-fpm
service php-fpm restart
,不过这种情况请求会中断, 或者重载service php-fpm reload
,重载相对于重启则平顺很多,不会导致用户请求直接中断,相对来说风险低很多,但是 php-fpm 收到reload信号,便会向所有子进程发送 SIGGUIT 信号,同时注册一个定时器,在规定的时间之内子进程没有退出,接着在发送 SIGTERM 信号,结束子进程。如果在一秒之内子进程还是没结束 直接发送SIGKILL 强制杀死,这时候 nginx 就会返回 502 错误码 - 配置
opcache.validate_timestamps=1
和opcache.revalidate_freq=xx
, 以秒为单位,那么 OPcache 会每隔opcache.revalidate_freq
设定的秒数 检查脚本是否更新, 如果发现内存中的时间晚于文件更新时间,将会更新缓存 - OPcache 达到最大内存消耗,也就是
opcache.memory_consumption
这个配置,Opcache 就会尝试重新启动缓存 - OPcache 达到最大加速文件,也就是
opcache.max_accelerated_files
这个配置,Opcache 就会尝试重新启动缓存 - OPcache 浪费的内存超过了配置的
opcache.max_wasted_percentage
,Opcache 就会尝试重新启动缓存, 手动清空 wasted memory 只能重启 PHP-FPM 或使用 PHP 函数 opcache_reset()
需要注意的是,当 PHP 以 PHP-FPM 的方式运行的时候,OPcache 的缓存是无法通过 php 命令进行清除的,只能通过 http 或 cgi 到 php-fpm 进程的方式来清除缓存
如果 Opcache 不设置过期时间的话,应尽量避免流量高峰期发布代码,因为这时候清理缓存,可能会导致重复新建缓存。
1. 生产环境不设置缓存定时刷新时间
将刷缓存的时间(opcache.validate_timestamps
)设置为 0, 表示永不刷新,然后在每次 Jenkins 构建 php 程序的时候,调用一个脚本来手动刷新缓存
#!/bin/bash
WEBDIR=/var/www/html/
RANDOM_NAME=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 13)
echo "<?php opcache_reset(); ?>" > ${WEBDIR}${RANDOM_NAME}.php
curl http://localhost/${RANDOM_NAME}.php
rm ${WEBDIR}${RANDOM_NAME}.php
上面的脚本意思就是,先生成一个随机的字符串RANDOM_NAME
,长度为13,包含大写字母、小写字母和数字,接下来创建一个新的PHP文件,文件名为${RANDOM_NAME}.php
,并且这个文件在${WEBDIR}
目录下。这个PHP文件的内容是<?php opcache_reset(); ?>
, 接下来使用 curl 命令访问这个新创建的PHP文件。由于这个文件的内容是opcache_reset()
,所以这会导致服务器上的OPcache
被重置, 最后,删除这个临时创建的PHP文件
或者直接走 php 的 CLI 指令,不过这种方式如果是在 php-fpm 下是没有效果的,只能用上面那种 http 请求 php 文件的方式。
#!/bin/bash
php -r "opcache_reset();"
2. 增加 hugepage 配置
启用 opcache.huge_code_pages
将 PHP 代码(文本段)拷贝到 HUGE PAGES 中。这应该会提高性能,但是需要适当的 OS 配置。自 PHP 7.0.0 起可在 Linux 上使用,自 PHP 7.4.0 上可在 FreeBSD 上使用。opcache.huge_code_pages=1
同时需要在 /etc/sysctl.conf
设置系统分配的大页面(huge pages)的数量,比如下面就分配了512个大页面
$ cat /etc/sysctl.conf | grep huge
vm.nr_hugepages=512
3. 配置 preload 用于缓存预热
从 PHP 7.4.0 起,PHP 可以被配置为在引擎启动时将一些脚本预加载进 opcache 中opcache.preload=preload.php
版权声明:本文为原创文章,版权归 全栈开发技术博客 所有。
本文链接:https://www.lvtao.net/tool/php-opcache.html
转载时须注明出处及本声明