生产环境抛 `Class 'Redis' not found`,我却在 PHP 扩展列表里看到了打勾的 redis

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

上周凌晨两点,监控群里突然炸出一条异常:订单队列消费停滞,报错 Class 'Redis' not found。我揉着眼睛连上服务器,宝塔面板里 Redis 扩展明晃晃打着绿勾,php -m | grep redis 也有输出,整个人当场清醒——这戏码我熟,但熟的是另一种剧本。

先说我怎么快速区分的。Exception 这玩意儿,看消息不如看堆栈第一行。这次堆栈顶是 think\queue\Connector\Redis,说明框架已经走到连接层了,不是配置填错地址那种 ConnectionException。但 "Class not found" 通常意味着 CLI 和 FPM 跑的不是同一个 PHP,或者扩展装到了非线程安全版上。

我这套环境是宝塔 + PHP 8.1,之前为了某个老插件切过 NT S/TS 版本。查 phpinfo() 的 Server API 是 FPM/FastCGI,再跑 php -i | grep 'Server API' 显示 Command Line Interface——到这里还没发现问题。真正露馅的是 php --ini 和 FPM 的 phpinfo() 里 Loaded Configuration File 路径不同,一个指向 /www/server/php/81/etc/php-cli.ini,另一个是 php-fpm.ini。宝塔的扩展管理默认只动 FPM 那份,我月初手欠用 pecl 给 CLI 装了个 redis,FPM 这边其实没装上,面板里的勾是历史缓存。

这种"两边不一致"的坑,我归类成环境分裂型。快速定位就三句话:CLI 和 FPM 各跑一遍 php -mphp --ini 对一遍路径,有 OPcache 的先 kill -USR2 reload 再看。别信面板,面板是给人看的,不是给机器看的。

再说另一类我踩更多的:框架封装型。ThinkPHP 抛 think\exception\ClassNotFoundException 的时候,消息里经常带个 app\service\XXX 的类名,但根因可能是 composer autoload 没更新,或者你改了命名空间却忘了 composer dump-autoload。这种我现在的习惯是,先 grep -r "class XXX" 确认文件存在,再 composer dump-autoload -o 走一遍,比盯着 Exception 消息死磕快十倍。

还有种最阴的,延迟爆炸型。MySQL 抛 PDOException SQLSTATE[HY000] [2002] 连接拒绝,不一定是 MySQL 挂了,可能是你用了持久连接,PHP-FPM worker 进程里的连接句柄在数据库重启后变成僵尸,新请求复用旧句柄直接炸。这种看错误日志会误导你去查 mysqld 状态,实际 pdo_mysqlPDO::ATTR_PERSISTENT 设为 false 或者 FPM reload 就能验证。我的土办法:遇到连接类异常,先 netstat -anp | grep :3306 看状态,如果是 CLOSE_WAIT 堆积,基本就是这类。

现在我的 Nginx 错误日志目录下放了个 exceptions.sh,按异常类型做了简单分类:Fatal error 级别先看内存限制和超时配置,Parse error 基本是上线前没做语法检查,Warning 级别的那种 foreach on bool 往往是上游接口返回格式变了。不追求自动化,就图个凌晨两点脑子不清醒时有个 checklist 能照着点。

你们有没有那种"报错消息完全误导方向"的经历?我上次被 SSL certificate problem 骗去 renew 证书,最后发现是服务器时间漂移了五分钟。

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