PHP缓存加速介绍
操作码
操作码是PHP编译后的一个中间文件,是一个字节码。
nginx并发最大万,PHP并发次之几百,MySQL最小 几百,想要网站的并发增大,首先需要优化PHP和MySQL。
PHP的解析时,在服务端解析PHP语言会生成一个特定的操作码,默认情况下PHP在程序执行后就会删除此操作码,而操作码缓存的原理是将编译后的操作码保存下来,并放入到共享内存中,以便在下次调用该PHP页面时重用它,避免了相同代码的重复编译,节省了PHP引擎重复编译的时间,降低了服务器负载,同时减少了CPU和内存开销
PHP操作码缓存原理图
互联网企业最大的瓶颈是PHP和MySQL的瓶颈
流程说明:
1、nginx接收客户端的PHP程序访问请求
2、nginx根据扩展名等过滤规则将PHP程序请求传递给解析PHP的fcgi进程(php-fpm)
3、php-fpm进程调用PHP解析器读取站点磁盘上的PHP文件,并加载到内存中
4、PHP解析器将PHP程序编译成opcode文件,然后将opcode缓存起来
5、php-fpm引擎执行opcode之后,返回数据给nginx,进而返回给客户端
6 、nginx收到新的PHP请求时,php-fpm引擎将会直接读取缓存中的opcode并执行,将结果返回。
Opcache
1. 概述
在理解 OPCache 功能之前,我们有必要先理解PHP-FPM + Nginx 的工作机制,以及PHP脚本解释执行的机制。
1.1 PHP-FPM + Nginx 的工作机制
第一步:启动服务
启动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 → 第二个 → 以此类推 → 最终响应发送给客户端
1.2 PHP脚本解释执行的机制
了解了PHP + Nginx 整体的处理流程后,我们接下来看一下PHP脚本具体执行流程,
首先我们看一个实例:
<?php
if (!empty($_POST)) {
echo “Response Body POST: “, json_encode($_POST), “\n”;
}
if (!empty($_GET)) {
echo “Response Body GET: “, json_encode($_GET), “\n”;
}
我们分析一下执行过程:
1、php初始化执行环节,启动Zend引擎,加载注册的扩展模块
2、初始化后读取脚本文件,Zend引擎对脚本文件进行词法分析(lex),语法分析(bison),生成语法树
3、Zend 引擎编译语法树,生成opcode,
4、Zend 引擎执行opcode,返回执行结果
在PHP cli模式下,每次执行PHP脚本,四个步骤都会依次执行一遍;
在PHP-FPM模式下,步骤1)在PHP-FPM启动时执行一次,后续的请求中不再执行;步骤2)~4)每个请求都要执行一遍;
其实步骤2)、3)生成的语法树和opcode,同一个PHP脚本每次运行的结果都是一样的,
在PHP-FPM模式下,每次请求都要处理一遍,是对系统资源极大的浪费,那么有没有办法优化呢?
当然有,如:
OPCache:前身是Zend Optimizer+ ,是 Zend Server 的一个开源组件;官方出品,强力推荐
APC:Alternative PHP Cache 是一个开放自由的 PHP opcode 缓存组件,用于缓存、优化 PHP 中间代码;已经不更新了不推荐
APCu:是APC的一个分支,共享内存,缓存用户数据,不能缓存opcode,可以配合Opcache 使用
eAccelerate:同样是不更新了,不推荐
xCache:不再推荐使用了
2. OPCache 介绍
OPCache 是Zend官方出品的,开放自由的 opcode 缓存扩展,还具有代码优化功能,省去了每次加载和解析 PHP 脚本的开销。
PHP 5.5.0 及后续版本中已经绑定了 OPcache 扩展。
缓存两类内容:
OPCode
Interned String,如注释、变量名等
Opcache 原理
OPCache缓存的机制主要是:将编译好的操作码放入共享内存,提供给其他进程访问。
这里就涉及到内存共享机制,另外所有内存资源操作都有锁的问题,我们一一解读。
3.1 共享内存
UNIX/Linux 系统提供很多种进程间内存共享的方式:
1.System-V shm API:
System V共享内存,sysv shm是持久化的,除非被一个进程明确的删除,否则它
始终存在于内存里,直到系统关机;
2.mmap API:
mmap映射的内存在不是持久化的,如果进程关闭,映射随即失效,除非事先已
经映射到了一个文件上
内存映射机制mmap是POSIX标准的系统调用,有匿名映射和文件映射两种
mmap的一大优点是把文件映射到进程的地址空间
避免了数据从用户缓冲区到内核page cache缓冲区的复制过程;
当然还有一个优点就是不需要频繁的read/write系统调用
3.POSIX API:System V 的共享内存是过时的, POSIX共享内存提供了使用更简单、设计更合理的API.
4.Unix socket API
OPCache 使用了前三个共享内存机制,根据配置或者默认mmap 内存共享模式。
依据PHP字节码缓存的场景,OPCache的内存管理设计非常简单,快速读写,不释放内存,过期数据置为Wasted。
当Wasted内存大于设定值时,自动重启OPCache机制,清空并重新生成缓存。
3.2 互斥锁
任何内存资源的操作,都涉及到锁的机制。
共享内存:一个单位时间内,只允许一个进程执行写操作,允许多个进程执行读操作;
写操作同时,不阻止读操作,以至于很少有锁死的情况。
这就引发另外一个问题:新代码、大流量场景,进程排队执行缓存opcode操作;重复写入,导致资源浪费。
4. OPCache 缓存解读
OPCache 是官方的Opcode 缓存解决方案,在PHP5.5版本之后,已经打包到PHP源码中一起发布。
它将PHP编译产生的字节码以及数据缓存到共享内存中, 在每次请求,从缓存中直接读取编译后的opcode,进行执行。
通过节省脚本的编译过程,提高PHP的运行效率。
如果正在使用APC扩展,做同样的工作,现在强烈推荐OPCache来代替,尤其是PHP7中。
4.1 OPCode 缓存
Opcache 会缓存OPCode以及如下内容:
PHP脚本涉及到的函数
PHP脚本中定义的Class
PHP脚本文件路径
PHP脚本OPArray
PHP脚本自身结构/内容
4.2 Interned String 缓存
首先我们需要理解,什么是 Interned String?
在PHP5.4的时候, 引入了Interned String机制, 用于优化PHP对字符串的存储和处理。
尤其是处理大块的字符串,比如PHP doces时,Interned String 可以优化内存。
Interned String 缓存的内容包括: 变量名称、类名、方法名、字符串、注释等。
在PHP-FPM模式中,Interned String 缓存字符,仅限于Worker 进程内部。
而缓存到OPCache中,那么Worker进程之间可以使用 Interned String 缓存的字符串,节省内存。
我们需要注意一个事情,在PHP开发中,一般会有大段的注释,也会被缓存到OPCache中。
可以通过php.ini的配置,关闭注释的缓存。
但是,像Zend Framework等框架中,会引用注释,所以,是否关闭注释的缓存,需要区别对待。
5. OPCache 更新策略
是缓存,都存在过期,以及更新策略等。
而OPCache的更新策略非常简单,到期数据置为Wasted,达到设定值,清空缓存,重建缓存。
这里需要注意:在高流量的场景下,重建缓存是一件非常耗费资源的事儿。
OPCache 在创建缓存时并不会阻止其他进程读取。
这会导致大量进程反复新建缓存。所以,不要设置OPCache过期时间
每次发布新代码时,都会出现反复新建缓存的情况。如何避免呢?
不要在高峰期发布代码,这是任何情况下都要遵守的规则
代码预热,比如使用脚本批量调PHP 访问URL,或者使用OPCache 暴露的API 如opcache_compile_file()
进行编译缓存
6. OPCache 的配置
PHP 的正常执行流程如下
Request 请求(Nginx,Apache,Cli 等)→ Zend 引擎读取.php 文件 → 扫描其词典和表达式 → 解析文件 → 创建要执行的计算机代码 (称为 Opcode) → 最后执行 Opcode → Response 返回
每一次请求 PHP 脚本都会执行一遍以上步骤,如果 PHP 源代码没有变化,那么 Opcode 也不会变化,显然没有必要每次都重新生成 Opcode,结合在 Web 中无所不在的缓存机制,我们可以把 Opcode 缓存下来,以后直接访问缓存的 Opcode 岂不是更快,启用 Opcode 缓存之后的流程图如下所示:
6.1 内存配置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
6.2 允许缓存的文件数量以及大小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
6.3 注释相关的缓存opcache.load_commentsboolean
如果禁用,则即使文件中包含注释,也不会加载这些注释内容。本选项可以和 opcache.save_comments
一起使用,以实现按需加载注释内容。opcache.fast_shutdown
boolean 如果启用,则会使用快速停止续发事件。所谓快速停止续发事件是指依赖 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
。需要显示的启用文件缓存。
7 配置信息
zend_extension=opcache.so
; Zend Optimizer + 的开关, 关闭时代码不再优化.
opcache.enable=1
; Determines if Zend OPCache is enabled for the CLI version of PHP
opcache.enable_cli=1
; Zend Optimizer + 共享内存的大小, 总共能够存储多少预编译的 PHP 代码(单位:MB)
; 推荐 128
opcache.memory_consumption=64
; Zend Optimizer + 暂存池中字符串的占内存总量.(单位:MB)
; 推荐 8
opcache.interned_strings_buffer=4
; 最大缓存的文件数目 200 到 100000 之间
; 推荐 4000
opcache.max_accelerated_files=2000
; 内存“浪费”达到此值对应的百分比,就会发起一个重启调度.
opcache.max_wasted_percentage=5
; 开启这条指令, Zend Optimizer + 会自动将当前工作目录的名字追加到脚本键上,
; 以此消除同名文件间的键值命名冲突.关闭这条指令会提升性能,
; 但是会对已存在的应用造成破坏.
opcache.use_cwd=0
; 开启文件时间戳验证
opcache.validate_timestamps=1
; 2s检查一次文件更新 注意:0是一直检查不是关闭
; 推荐 60
opcache.revalidate_freq=2
; 允许或禁止在 include_path 中进行文件搜索的优化
;opcache.revalidate_path=0
; 是否保存文件/函数的注释 如果apigen、Doctrine、 ZF2、 PHPUnit需要文件注释
; 推荐 0
opcache.save_comments=1
; 是否加载文件/函数的注释
;opcache.load_comments=1
; 打开快速关闭, 打开这个在PHP Request Shutdown的时候会收内存的速度会提高
; 推荐 1
opcache.fast_shutdown=1
;允许覆盖文件存在(file_exists等)的优化特性。
;opcache.enable_file_override=0
; 定义启动多少个优化过程
;opcache.optimization_level=0xffffffff
; 启用此Hack可以暂时性的解决”can’t redeclare class”错误.
;opcache.inherited_hack=1
; 启用此Hack可以暂时性的解决”can’t redeclare class”错误.
;opcache.dups_fix=0
; 设置不缓存的黑名单
; 不缓存指定目录下cache_开头的PHP文件. /png/www/example.com/public_html/cache/cache_
;opcache.blacklist_filename=
; 通过文件大小屏除大文件的缓存.默认情况下所有的文件都会被缓存.
;opcache.max_file_size=0
; 每 N 次请求检查一次缓存校验.默认值0表示检查被禁用了.
; 由于计算校验值有损性能,这个指令应当紧紧在开发调试的时候开启.
;opcache.consistency_checks=0
; 从缓存不被访问后,等待多久后(单位为秒)调度重启
;opcache.force_restart_timeout=180
; 错误日志文件名.留空表示使用标准错误输出(stderr).
;opcache.error_log=
; 将错误信息写入到服务器(Apache等)日志
;opcache.log_verbosity_level=1
; 内存共享的首选后台.留空则是让系统选择.
;opcache.preferred_memory_model=
; 防止共享内存在脚本执行期间被意外写入, 仅用于内部调试.
;opcache.protect_memory=0
8.HugePage
- HugePage 原理
通过启用这个特性,PHP7 会把自身的 TEXT 段(执行体)” 挪 “到 Hugepage 上,之前的测试,我们能稳定的在 WordPress 上看到 2%~3% 的 QPS 提升。
关于 Hugepage 是啥,简单的说下就是默认的内存是以 4KB 分页的,而虚拟地址和内存地址是需要转换的, 而这个转换是要查表的,CPU 为了加速这个查表过程都会内建 TLB(Translation Lookaside Buffer), 显而易见如果虚拟页越小,表里的条目数也就越多,而 TLB 大小是有限的,条目数越多 TLB 的 Cache Miss 也就会越高, 所以如果我们能启用大内存页就能间接降低这个 TLB Cache Miss,至于详细的介绍,Google 一搜一大堆我就不赘述了,这里主要说明下如何启用这个新特性, 从而带来明显的性能提升。
2.HugePage 配置
$ sudo sysctl vm.nr_hugepages=512 // 切勿越大越好,会长占内存
分配 512 个预留的大页内存:
$ cat /proc/meminfo | grep Huge
AnonHugePages: 106496 kB
HugePages_Total: 512
HugePages_Free: 504
HugePages_Rsvd: 27
HugePages_Surp: 0
Hugepagesize: 2048 kB
然后在 php.ini 中加入:
opcache.huge_code_pages=1
Opcache file cache
- Opcache file cache 介绍
使用 opcache 把编译后的 php 文件存储为文件,实现 php 源码保护和脚本加速,会有很明显的性能提升 - Opcache file cache 配置
在 php.ini 中加入:
opcache.file_cache=/tmp
这样 PHP 就会在 /tmp 目录下 Cache 一些 Opcode 的二进制导出文件,可以跨 PHP 生命周期存在.