宝塔面板装完Redis扩展,ThinkPHP6缓存死活不走内存的排查实录

小助手
小助手 版主圣羽星庭 勋望元宿志愿先锋
社区管理
站长杂谈 100 浏览 0 回复

昨晚给一个新项目搭环境,宝塔里PHP8.1的Redis扩展显示已安装,phpinfo也正常,ThinkPHP6里配置了'type' => 'redis',结果压测一看,全走的数据库查询。折腾了三个多小时,记录一下完整踩坑链,给同样懵的兄弟参考。

第一层假象:扩展装了就万事大吉

宝塔的软件商店点安装Redis,PHP扩展里勾选redis,重启PHP。常规流程对吧?但我漏了一个点——ThinkPHP6的redis缓存驱动依赖的是phpredis扩展,而宝塔默认装的是igbinary+redis的组合,某些PHP版本里这俩会有协议兼容问题。表现为Cache::get()不报错,但永远返回null,程序默默回退到数据库。

验证方法:别只看phpinfo,直接写个裸PHP文件测$redis = new Redis(); $redis->connect('127.0.0.1', 6379);,如果这里就报Redis server went away或者连接超时,跟TP框架无关,先解决底层。

第二层坑:Unix Socket权限陷阱

我为了省TCP开销,让TP走/tmp/redis.sock。宝塔安装的Redis默认socket文件权限是redis:redis 755,而PHP-FPM进程用户是www:www。结果框架配置里写'socket' => '/tmp/redis.sock',连接时直接被权限拒绝。

诡异的是TP的缓存驱动在这里没抛异常,而是静默失败,然后按配置回退。我直到开debug=>true看日志才发现Connection refused的蛛丝马迹。解决办法:改redis.confunixsocketperm为777,或者干脆让PHP-FPM和Redis同用户组——但生产环境别777,我是临时测试才这么干。

第三层:路由中间件里初始化缓存的时序问题

这项目有个全局中间件做IP限流,逻辑写在app/middleware/Throttle.php,里面直接Cache::store('redis')->get('ip:'.$ip)。问题来了——中间件的执行时机早于应用初始化完成,此时Redis连接池还没准备好,TP又双叒叕静默降级到文件缓存。限流数据写在文件里,重启就没了,压测时表现为限流完全失效。

排查时我在中间件里加var_dump(Cache::store('redis')->handler());,返回的是Redis实例对象,看起来正常。但抓包发现根本没往6379发包,最后跟到think\cache\Driver的初始化逻辑,才发现中间件阶段app->initialized为false时,驱动会走简化分支。

解决:把限流逻辑从中间件移到控制器基类的initialize()里,或者中间件里强制app->initialize()后再操作缓存。代价是损失一点点性能,但至少逻辑对了。

第四层:钩子不触发的隐藏条件

项目用了个第三方登录插件,文档说在user_login钩子写入缓存标记登录态。我按文档配了事件监听,登录成功但Redis里永远没有key。debug跟进去,Event::trigger('user_login')确实执行了,但插件的监听器类里用了Cache::tag('session')->set(...),而我没开TP的缓存标签功能——cache.php'tag_prefix' => ''时,tag相关操作直接跳过。

更坑的是这个跳过是静默的,set()返回true,实际没写进去。插件作者估计也没测过Redis关闭tag的场景。

最后汇总验证清单:

1. 裸PHP连Redis通不通 → 排除扩展/服务本身
2. socket/tcp权限对不对 → ls -l /tmp/redis.socktelnet 127.0.0.1 6379
3. 框架debug日志开没开 → TP的silent fail是最大敌人
4. 缓存操作时机对不对 → 中间件/钩子/初始化顺序
5. 高级功能依赖开没开 → tag、持久化、序列化协议

现在项目跑通了,但回头想想,这三个小时至少两小时花在"它没报错所以应该没问题"的惯性思维上。框架封装得太好,有时候反而是排查的阻碍。有类似经历的兄弟欢迎交流,你们还踩过哪些静默失败的坑?

评论0
回复 · 0
还没有回复
微信客服 微信客服