根本原因是Hyperf每次启动都重复执行CPU密集型的反射解析;开启scan_cacheable后,若runtime/container/缓存文件存在且SCAN_CACHEABLE=true,框架直接加载预生成元数据,跳过全部扫描。

Hyperf 注解扫描慢,根本原因不是代码多,而是每次启动都重复做反射解析 —— 开启 scan_cacheable 后,只要缓存文件存在,就跳过整个扫描过程,直接加载预生成的元数据。
为什么注解扫描会拖慢启动速度
Hyperf 在启动时会用 ReflectionClass 遍历所有类,逐个读取 #[Controller]、#[Inject]、#[GetMapping] 这类注解。这个过程是 CPU 密集型操作,且无法并行加速。尤其当 app/ 下模型多、vendor/ 组件大(比如带大量注解的 SDK),单次扫描可能耗时数秒甚至十几秒。
常见错误现象:
- 部署后服务“卡住”几秒才响应第一个请求
-
php bin/hyperf.php start执行后长时间无日志输出 - Docker 容器反复重启(因健康检查超时)
这不是 PHP 性能差,而是没让框架“记住上次看到什么”。
开启 scan_cacheable 的硬性前提
光在配置里写 'cacheable' => true 不够,必须满足三个条件,缺一不可:
-
config/autoload/annotations.php中'scan' => ['enable' => true]已启用(默认开) - 环境变量或配置中明确设置
SCAN_CACHEABLE=true(推荐写进config/autoload/constants.php或.env) -
runtime/container/目录可写,且首次启动已成功生成缓存文件(检查是否存在runtime/container/annotation/和runtime/container/proxy/子目录)
注意:scan_cacheable 是开关,不是缓存策略 —— 它只决定“要不要跳过扫描”,不参与缓存内容生成逻辑。
runtime/container/ 缓存文件怎么来的
它不是自动“悄悄生成”的,而是由 di:init-proxy 命令触发的。生产部署必须显式执行一次:
php bin/hyperf.php di:init-proxy
这个命令会:
- 清空旧的
runtime/container/ - 扫描
app/和符合条件的vendor/目录 - 把注解提取结果序列化为 PHP 数组,写入
runtime/container/annotation/下的.php文件 - 把 AOP 代理类代码写入
runtime/container/proxy/
后续每次启动,只要 SCAN_CACHEABLE=true 且这些文件存在,Hyperf 就直接 include 它们,不再碰反射 API。
容易被忽略的加密与容器化陷阱
如果你用代码加密或 Docker 多阶段构建,runtime/container/ 是唯一不能靠“运行时生成”的部分:
- 加密时:必须保留
runtime/container/目录(含所有.php缓存文件),且确保加密后仍可被 PHP 读取 - Docker 构建时:不能把
runtime/放进.dockerignore,也不能在ENTRYPOINT里删掉它再重跑di:init-proxy—— 那等于放弃缓存 - K8s 滚动更新时:新 Pod 必须挂载或复制一份已生成好的
runtime/container/,否则每个 Pod 启动都扫一遍
最稳妥的做法是:在 CI/CD 流水线最后一步生成缓存,再打包镜像。别指望线上机器自己“热身”。











