ThinkPHP 启动预热必须用 ApplicationRunner,确保容器、Redis、DB 就绪;需健康检查、TTL 随机化、分批写入、原生查询、统一 key 前缀、可重入标记及内存与命名一致性校验。

ThinkPHP 启动后自动执行预热逻辑用 ApplicationRunner 最稳
别在 command 命令里硬写预热脚本,也别依赖 php think cache:clear 后手动跑——ThinkPHP 6+ 的 ApplicationRunner 是唯一能确保「容器已初始化、Redis 连接已就绪、数据库服务已可用」的钩子点。用 @PostConstruct 或 InitializingBean 在 ThinkPHP 里不生效,因为不是 Spring 生态。
实操建议:
- 新建
app/runner/CacheWarmer.php,实现think\contract\ApplicationRunnerInterface - 在
run()方法里注入think\Cache和think\Db,避免直接 new Redis 实例(绕过配置) - 必须加健康检查:先调
$cache->get('health:ping'),再写入;失败则throw new RuntimeException('Redis unreachable'),让启动中断,不带病上线 - 不要在预热中调用模型的
with()或复杂关联——查多少字段就 select 多少,否则内存暴涨
cache()->set() 批量写入时 TTL 必须带随机偏移
全量预热时如果所有 key 都设固定 3600 秒过期,一小时后集体失效,就是缓存雪崩的起点。ThinkPHP 的 cache()->set($key, $value, $ttl) 支持整数或数组,但必须自己加随机逻辑。
常见错误现象:预热完命中率 95%,一小时后突降至 10%,DB CPU 拉满。
立即学习“PHP免费学习笔记(深入)”;
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
实操建议:
- 对每个 key 单独算 TTL:
$ttl = 3600 + rand(0, 600),范围控制在 ±10 分钟内足够打散 - 避免用
cache()->handler()->setMultiple()(底层是mset),它不支持 TTL;必须走单 keyset()循环 - 如果数据量超 5000 条,手动分批:
array_chunk($data, 500),每批后usleep(50000)(50ms),防 Redis 客户端缓冲区溢出
预热数据源选 Db::query() 而非模型,且禁用查询日志
用 app\model\User::where('status', 1)->select() 预热,会触发模型全部 boot()、append、hidden 逻辑,还可能加载冗余关联字段,慢三倍不止。更糟的是,默认开启的查询日志会把每条 SQL 写进 runtime/log,几万条记录直接撑爆磁盘。
实操建议:
- 直连原生查询:
Db::query("SELECT id,name,avatar FROM user WHERE status = ?", [1]) - 显式关闭日志:
Db::startTrans(); Db::setLogLevel('off');(注意:仅限预热上下文) - 序列化前用
json_encode($row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),避免 Redis 存储乱码或额外转义字符 - key 命名强制统一前缀:
'user:'. $row['id'],别用md5(json_encode($row))——不可读、无法 debug、占更多内存
预热失败必须留可重入标记,避免重复刷库
脚本中途崩溃(比如网络抖动、Redis 写满、OOM kill),下次启动又从头开始,轻则重复写入浪费资源,重则覆盖掉用户刚更新的缓存值(无版本控制时)。
实操建议:
- 预热前先写一个标记:
cache()->set('warmup:status:user', 'started', 3600);成功后改写为'done' - 每次启动先查这个标记,如果是
'started',说明上次异常中断,跳过该模块或发告警,不自动重试 - 标记 key 必须设 TTL,但不能太短——设 2 小时,既防长期卡死,也不至于误判正常重启
- 别把标记存在数据库里:预热就是为了减 DB 压力,自己又去查 DB,本末倒置
redis-cli --bigkeys 抽样测单条数据大小,如果平均超 2KB,就得压缩或拆分;而所有预热 key 必须和业务代码里读取的 key 完全一致——大小写、冒号数量、是否带版本号,差一个字符就等于白干。


















