使用 Swoole Compiler 加密后,不再需要 PHP 源代码,因此不再需要对PHP源码进行词法分析、语法分析和生成Opcode字节码。二进制目标文件只有经过处理的 opcode 二进制指令和内存数据,在第一次运行时将指令和数据载入内存。通常加密后的代码运行时性能损耗会增加10%~15%,最高不超过20%。
对全部或大部分PHP代码加密
Swoole Compiler 软件本身就是将 PHP 代码编译为经过特殊处理的opcode,所以使用Swoole Loader运行加密后的程序时,不需要其他OpCache扩展,包括opcache、apc、eAccelerator等opcode缓存扩展。
仅对少部分PHP代码加密
若项目中,仅少部分PHP代码进行了加密,大部分代码未加密。则需要同时开启opcache和swoole_loader来提高性能。当swoole_loader发现是非加密代码时,会将编译控制权转给opcache,由opcache实现代码的加载。
Opcache JIT 引起的 502 问题
请注意 Opcache JIT 与 Swoole Loader 存在不兼容,若要同时使用 swoole_loader 和 opcache 时必须要修改 php.ini 关闭 opcache.jit
opcache.jit=0
Opcache 配置
同时开启opcache和swoole_loader时,必须只使用OPcache文件缓存,并关闭JIT,否则可能会发生崩溃
推荐配置如下:
opcache.enable=1
; Swoole、Hyperf、WorkerMan 等 CLI 常驻进程需要开启
opcache.enable_cli=1
opcache.jit=0
opcache.file_cache=/tmp/php-opcache
opcache.file_cache_only=1
启动PHP进程前,需要创建opcache.file_cache所指定的目录,并确保PHP运行用户对该目录有读写权限。
- 推荐使用专用目录,例如
/tmp/php-opcache或/dev/shm/php-opcache,不要直接使用/tmp或/dev/shm opcache_get_status()函数无法查询文件缓存状态,该函数仅对共享内存缓存有效opcache.file_cache与下文的swoole_loader.cache_dir是两套不同的缓存,不能互相替代
加密文件缓存
Swoole Loader 加载加密文件时,需要打开文件、读取文件头判定类型、完成PHP版本与License校验,再读取加密数据进行解密。若加密代码存放于NFS网络文件系统或机械磁盘,这些读取会产生大量IOPS(尤其是getattr等元数据操作),导致iowait升高、系统负载上升。
Swoole Loader 3.2.5及以上版本支持加密文件缓存:首次加载后将数据缓存到本地目录,之后命中缓存时不再访问源文件所在的NFS或磁盘。
该功能仅在Linux平台生效
开启缓存
修改php.ini:
swoole_loader.cache_enable=1
swoole_loader.cache_dir=/dev/shm/swoole-loader-cache
swoole_loader.cache_lifetime=60
配置项
| 配置项 | 默认值 | 说明 |
|---|---|---|
swoole_loader.cache_enable |
0 |
是否开启缓存,默认关闭 |
swoole_loader.cache_dir |
/dev/shm/swoole-loader-cache |
缓存目录,必须为绝对路径 |
swoole_loader.cache_lifetime |
60 |
缓存有效期,单位为秒,设置为0表示永不过期 |
这三个配置项均为PHP_INI_SYSTEM级别,只能在php.ini中修改。
Loader会在cache_dir下按运行用户创建子目录swoole-loader-<uid>(属主为该用户,权限0700),缓存文件命名为<md5>.slc。若cache_dir本身允许其他用户写入(如/tmp、/dev/shm),该目录必须带有sticky位,否则缓存不会生效。
工作原理
- 首次加载某个文件时,
Loader读取源文件并判定文件类型,将读到的加密数据原样写入本地缓存 - 后续加载命中缓存时,直接从本地缓存读取,不再
open、stat源文件,路径解析也由缓存直接返回 - 缓存超过
cache_lifetime后,由其中一个进程读取源文件并刷新缓存,刷新期间其他进程继续使用已有缓存,不会同时涌向NFS - 每次加载依然会执行
License校验与解密,缓存只是替换了数据的读取位置,不降低安全性 - 未加密文件同样会生成缓存条目,但仅记录文件类型,代码数据仍由
opcache负责缓存(见上文)
使用建议
- 推荐将
cache_dir设置为内存文件系统/dev/shm,完全没有磁盘IO;加密文件较多、内存有限时可改用本地SSD目录 - 请勿将
cache_dir设置在NFS上,否则缓存自身也会产生NFS请求 - 多台机器共享同一份加密代码时,每台机器各自使用本地缓存目录,效果最好
- 同一台机器运行多个
PHP版本时,请为每个版本配置不同的cache_dir - 缓存写入失败(如
/dev/shm空间不足)会自动降级为直接读取源文件,不影响程序运行 - 缓存条目按文件路径生成,数量与加密文件数量相当;发布目录变化后旧条目不会自动清理,可手动删除缓存目录
验证与清理
ls /dev/shm/swoole-loader-cache/swoole-loader-*
能列出*.slc文件即表示缓存已生效,此时再用nfsstat -c观察,getattr、read等操作次数应明显下降。
需清理缓存时,停止PHP进程后直接删除缓存目录即可。
挂载 NFS 网络文件系统
若加密后的代码存放于NFS系统,可能会导致文件系统元数据IOPS升高,从而引发系统负载升高。使用下面的命令查看:
开启加密文件缓存后,命中缓存的请求不再访问NFS,以下的挂载参数优化可作为补充手段
nfsstat -c
Client rpc stats:
calls retrans authrefrsh
345600562 0 346852200
Client nfs v3:
null getattr setattr lookup access readlink
0 0% 343637891 99% 63 0% 137777 0% 2210824 0% 1 0%
read write create mkdir symlink mknod
1989 0% 20444 0% 391 0% 0 0% 0 0% 0 0%
remove rmdir rename link readdir readdirplus
34 0% 2 0% 38 0% 0 0% 97 0% 7495 0%
fsstat fsinfo pathconf commit
111 0% 2 0% 1 0% 9 0%
可以看到getattr操作次数非常多,需要对NFS挂载参数进行优化,减少NFS文件系统RPC次数。
1. 调整挂载选项
mount -o remount,actimeo=60,acregmin=60,acregmax=60,acdirmin=60,acdirmax=60 /www/wwwroot
这将设置属性缓存的有效期为 60 秒,可以显著减少网络请求。
2. 使用noatime选项
mount -o remount,noatime /www/wwwroot
这可以减少不必要的访问时间更新。
3. 考虑使用NFSv4
NFSv4 提供了更好的缓存机制,可以减少网络往返。