opcache 优化
OPcache 通过将 PHP 脚本预编译的字节码存储到共享内存中来提升 PHP 的性能,存储预编译字节码的好处就是省去了每次加载和解析 PHP 脚本的开销。
PHP-FPM + Nginx 的工作机制
在理解 OPCache 功能之前,我们有必要先理解 PHP-FPM + Nginx 的工作机制,以及 PHP 脚本解释执行的机制。请求从 Web 浏览器到 Nginx,再到 PHP 处理完成,一共要经历如下五个步骤:
- 第一步:启动服务
- 启动 PHP-FPM。PHP-FPM 支持两种通信模式:TCP socket 和 Unix socket。
- 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 请求。
- 通过 TCP socket 或者 Unix Socket 发送给 PHP-FPM 的 master 进程。
- 第四步:PHP-FPM Master => Worker
- PHP-FPM master 进程接收到请求。
- 分配 Worker 进程执行 PHP 脚本,如果没有空闲的 Worker,返回 502 错误。
- Worker(php-cgi)进程执行 PHP 脚本,如果超时,返回 504 错误。
- 处理结束,返回结果。
- 第五步:PHP-FPM Worker => Master => Nginx
- PHP-FPM Worker 进程返回处理结果,并关闭连接,等待下一个请求。
- PHP-FPM Master 进程通过 Socket 返回处理结果。
- Nginx Handler 顺序将每一个响应 buffer 发送给第一个 filter →第二个→以此类推→最终响应发送给客户端。
opcache 运行原理
OPCache 是 Zend 官方出品的,开放自由的 opcode 缓存扩展,还具有代码优化功能,省去了每次加载和解析 PHP 脚本的开销。PHP 5.5.0 及后续版本中已经绑定了 OPcache 扩展。
缓存两类内容:
- OPCode
- Interned String,如注释、变量名等
OPCache 缓存的机制主要是:将编译好的操作码放入共享内存,提供给其他进程访问。这里就涉及到内存共享机制,另外所有内存资源操作都有锁的问题,我们一一解读。
1、共享内存
UNIX/Linux 系统提供很多种进程间内存共享的方式:
- System-V shm API: System V 共享内存。sysv shm 是持久化的,除非被一个进程明确的删除,否则它始终存在于内存里,直到系统关机;
- mmap API:
- mmap 映射的内存在不是持久化的,如果进程关闭,映射随即失效,除非事先已经映射到了一个文件上。
- 内存映射机制 mmap 是 POSIX 标准的系统调用,有匿名映射和文件映射两种。
- mmap 的一大优点是把文件映射到进程的地址空间。
- 避免了数据从用户缓冲区到内核 page cache 缓冲区的复制过程。
- 当然还有一个优点就是不需要频繁的 read/write 系统调用。
- POSIX API:System V 的共享内存是过时的, POSIX 共享内存提供了使用更简单、设计更合理的 API。
- Unix socket API:OPCache 使用了前三个共享内存机制,根据配置或者默认 mmap 内存共享模式。依据PHP字节码缓存的场景,OPCache 的内存管理设计非常简单,快速读写,不释放内存,过期数据置为 Wasted。当Wasted内存大于设定值时,自动重启 OPCache 机制,清空并重新生成缓存。
opcache.preferred_memory_model="mmap"
:OPcache 首选的内存模块。如果留空,OPcache 会选择适用的模块,通常情况下,自动选择就可以满足需求。可选值包括:mmap,shm, posix 以及 win32。opcache.memory_consumption=64
:OPcache 的共享内存大小,以兆字节为单位,默认 64M。opcache.interned_strings_buffer=4
:用来存储临时字符串的内存大小,以兆字节为单位,默认 4M。opcache.max_wasted_percentage=5
:浪费内存的上限,以百分比计。如果达到此上限,那么 OPcache 将产生重新启动续发事件。默认 5。opcache.max_accelerated_files=2000
:OPcache 哈希表中可存储的脚本文件数量上限。真实的取值是在质数集合{223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987}中找到的第一个大于等于设置值的质数。设置值取值范围最小值是 200,最大值在 PHP 5.5.6 之前是 100000,PHP 5.5.6 及之后是 1000000。默认值 2000。opcache.max_file_size=0
:以字节为单位的缓存的文件大小上限。设置为 0 表示缓存全部文件。默认值 0。opcache.load_commentsboolean
:如果禁用,则即使文件中包含注释,也不会加载这些注释内容。本选项可以和 opcache.save_comments 一起使用,以实现按需加载注释内容。opcache.fast_shutdown boolean
:如果启用,则会使用快速停止续发事件。所谓快速停止续发事件是指依赖 Zend 引擎的内存管理模块一次释放全部请求变量的内存,而不是依次释放每一个已分配的内存块。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。需要显示的启用文件缓存。
2、互斥锁
任何内存资源的操作,都涉及到锁的机制。共享内存:一个单位时间内,只允许一个进程执行写操作,允许多个进程执行读操作;写操作同时,不阻止读操作,以至于很少有锁死的情况。这就引发另外一个问题:新代码、大流量场景,进程排队执行缓存opcode操作;重复写入,导致资源浪费。
OPCache 的配置
内存配置
允许缓存的文件数量以及大小
注释相关的缓存
二级缓存的配置
OPCache 配置说明
[opcache] ; 是否快开启opcache缓存。 ;opcache.enable=1 ; 是否在cli模式下开启opcache。 ;opcache.enable_cli=1 ; opcache共享内存的大小(单位是M)。 ;opcache.memory_consumption=128 ; 预留字符串的的内存大小(单位是M)。 ;opcache.interned_strings_buffer=8 ; 在hash表中存储的最大脚本文件数量,范围是200到1000000之间。实际的情况是在{ 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987 }中找到第一个大于等于设置值的质数。最小范围是200。 ;opcache.max_accelerated_files=10000 ; 浪费内存的上线,如果超过这个上线,opcache将重新启动。 ;opcache.max_wasted_percentage=5 ; 如果启用,opcache将会在hash表的脚本键后面增加一个文件目录,避免吃同名的脚本产生冲突。禁用的话可以提高性能,但是也容易导致应用不可用。 ;opcache.use_cwd=1 ; 如果启用(1),opcache会每隔设置的值时间来判断脚本是否更新。如果禁用(0),则不会自动检测脚本更新,必须通过重启PHP服务,或者使用opcache_reset()、opcache_invalidate()函数来刷新缓存。 ;opcache.validate_timestamps=1 ; opcache检查脚本是否更新的时间周期(单位是秒),如果设置为0则会针对每一个请求进行检查更新,如果validate_timestamps=0,该值不会生效。 ;opcache.revalidate_freq=60 ; 如果禁用,在统一include_path下面已经缓存的文件将被重用,因此无法找到该路径下的同名文件。 ;opcache.revalidate_path=0 ; 是否保存PHP脚本中的注释内容。禁用,则不会缓存PHP代码中的注释,可以减少文件中的体积,但是一些依赖注释或者注解将无法使用。 ;opcache.save_comments=1 ; 如果启用,则会使用快速停止续发事件。 所谓快速停止续发事件是指依赖 Zend 引擎的内存管理模块 一次释放全部请求变量的内存,而不是依次释放每一个已分配的内存块。 ; 在php7.2.0开始,被移除,这类说的事件将会在PHP中自动处理。 ;opcache.fast_shutdown=1 ; 如果启用,在调用file_exists()、is_file()和is_readable()函数时,不管文件是否被缓存,都会检测操作码。如果禁用,可能读取的内容是一些旧数据。 ;opcache.enable_file_override=0 ; 控制优化级别,是一个二进制的位的掩码。 ;opcache.optimization_level=0xffffffff ; 不进行编译优化的配置文件路径。该文件中配置具体哪些不被编译的文件。如果文中每行的开头是";"开头,则会被视为注释。黑名单中的文件名,可以是通配符,也可以使用前缀。 ; 例如配置文件的路径是"/home/blacklist.txt",则该配置的值就是该路径。 ; 配置的内容可以是如下格式 ; 这是一段注释,在解析的时候因为开头是;,则会被视为注释 ;/var/www/a.php ;/var/www/a/b.php ;opcache.blacklist_filename= ; 以字节为单位的缓存的文件大小上限。设置为 0 表示缓存全部文件。 ;opcache.max_file_size=0 ; 每个N次请求会检查缓存校验和,0是不检查。该项对性能有较大影响,尽量在调试环境中使用。 ;opcache.consistency_checks=0 ; 如果缓存处于非激活状态,等待多少秒之后计划重启。 如果超出了设定时间,则 OPcache 模块将杀除持有缓存锁的进程, 并进行重启。 ;opcache.force_restart_timeout=180 ; 错误日志文件位置,不填写将默认输出到服务器的错误日志文件中。 ;opcache.error_log= ; 错误日志文件等级。 ; 默认情况下,仅有致命级别(0)及错误级别(1)的日志会被记录。 其他可用的级别有:警告(2),信息(3)和调试(4)。 ; 如何设置的是1以上,在进行force_restart_timeout选项时,会将错误日志中插入一条警告信息。 ;opcache.log_verbosity_level=1 ; opcache首选的内存模块,不配置则自动选择。可以选择的值有mmap,shm, posix 以及 win32。 ;opcache.preferred_memory_model= ; 保护共享内存,以避免执行脚本时发生非预期的写入。 仅用于内部调试。 ;opcache.protect_memory=0 ; 只允许指定字符串开头的PHP脚本调用opcache api函数,默认不做限制。 ;opcache.restrict_api= ; 在 Windows 平台上共享内存段的基地址。 所有的 PHP 进程都将共享内存映射到同样的地址空间。 使用此配置指令避免“无法重新附加到基地址”的错误。 ;opcache.mmap_base= ; 配置二级缓存目录并启用二级缓存。 启用二级缓存可以在 SHM 内存满了、服务器重启或者重置 SHM 的时候提高性能。 默认值为空字符串 "",表示禁用基于文件的缓存。 ;opcache.file_cache= ; 启用或禁用在共享内存中的 opcode 缓存。 ;opcache.file_cache_only=0 ; 当从文件缓存中加载脚本的时候,是否对文件的校验和进行验证。 ;opcache.file_cache_consistency_checks=1 ; 在 Windows 平台上,当一个进程无法附加到共享内存的时候, 使用基于文件的缓存。需要开启opcache.file_cache_only选项。建议开启此选项,否则可能导致进程无法启动。 ;opcache.file_cache_fallback=1 ; 启用或者禁用将 PHP 代码(文本段)拷贝到 HUGE PAGES 中。 此项配置指令可以提高性能,但是需要在 OS 层面进行对应的配置。 ;opcache.huge_code_pages=1 ; 针对当前用户,验证缓存文件的访问权限。 ;opcache.validate_permission=0 ; 在 chroot 的环境中避免命名冲突。 为了防止进程访问到 chroot 环境之外的文件,应该在 chroot 的情况下启用这个选项。 ;opcache.validate_root=0