hyperf生产环境注解解析“缓慢”实为启动时未预热缓存,因runtime/container/proxy/为空或过期,导致每次启动全量扫描类、反射注解、生成代理类;需在ci/cd构建阶段执行di:proxy:generate并禁用运行时扫描。

Hyperf 生产环境下注解解析“缓慢”,本质不是运行时慢,而是启动时没做缓存预热——runtime/container/proxy/ 目录为空或过期,导致每次启动都重新扫描全部类、反射提取注解、生成代理类,这个过程在中大型项目里可能耗时数秒甚至十几秒。
为什么生产环境还会触发注解扫描?
常见原因有三个:
-
runtime/目录被清空或未挂载(如 Docker 容器每次重启丢掉 runtime) -
config/autoload/annotations.php中'scan' => ['enable' => false]没生效,或被其他配置覆盖 - 启用了开发模式相关组件(如
hyperf/devtool),即使APP_ENV=prod也会强制开启动态扫描
只要 scan.enable 为 true 且缓存文件缺失,Hyperf 就会在 BootstrapPhase 主动执行全量反射扫描——这不是 bug,是设计使然:它把“编译”工作压到启动期,换运行时零反射开销。
如何确认是否真在运行时解析注解?
别猜,直接看日志和文件:
- 启动日志中出现
Scanning annotations...或Generating proxy class for xxx,说明正在扫描 - 检查
runtime/container/proxy/下是否有大量*Proxy.php文件;若为空或只有零星几个,说明缓存未生成或失效 - 用
php bin/hyperf.php di:proxy:generate手动触发一次,观察耗时和生成文件数
提前生成代理类缓存的实操要点
核心就一条:让缓存生成动作脱离运行时,在构建/部署阶段完成。关键不是“怎么生成”,而是“在哪生成、谁来读取”:
- CI/CD 构建镜像时,在
php bin/hyperf.php start前加一步:php bin/hyperf.php di:proxy:generate,并确保runtime/被持久化进镜像(或作为 volume 挂载) - 禁用运行时扫描:在
config/autoload/annotations.php显式设为'enable' => false,否则即使有缓存,Hyperf 仍会先尝试扫描再 fallback 到缓存 - Phar 打包场景下,
BASE_PATH可能指向 phar:// 协议路径,需在annotations.php中改用绝对路径或适配 phar 的scan.paths - 注意
di:proxy:generate命令依赖完整 autoloader 和配置加载,务必在vendor/autoload.php可用、.env已加载的前提下执行
缓存生成后仍慢?检查这些隐藏点
生成了 proxy 文件不代表万事大吉:
-
opcache.enable_cli=1未开启:CLI 模式下 proxy 类 PHP 文件不会被 opcache 缓存,每次 require 都是真实文件 I/O -
runtime/权限问题:Web 进程用户(如 www-data)无权读取 proxy 文件,会静默 fallback 到运行时生成(且失败) - AOP 切面匹配逻辑太宽泛:比如
$classes = ['*']或正则过度匹配,会导致代理类爆炸式增长,磁盘体积大、加载慢 - 某些组件(如
hyperf/validation)在运行时仍会触发少量反射,不属于代理缓存范畴,需单独优化
真正影响上线体验的,从来不是单个注解解析耗时,而是启动那一刻整个扫描流程是否可控、可预测。把 di:proxy:generate 当作构建流水线里的一个标准步骤,比任何运行时调优都管用。











