1. 队列虽好,但“MySQL server has gone away”这个坑你踩过吗?
用ThinkPHP做开发的朋友,尤其是处理过邮件发送、数据报表生成这类耗时任务的,肯定对thinkphp-queue这个队列组件不陌生。它确实是个好东西,能把那些耗时的操作丢到后台慢慢跑,用户点了按钮马上就能得到响应,体验提升不是一点半点。我自己在项目里也重度依赖它,从订单处理到消息推送,几乎无处不在。
但不知道你有没有遇到过这种情况:队列任务在服务器上跑得好好的,跑着跑着,突然就报错了,日志里赫然出现一行刺眼的SQLSTATE[HY000]: General error: 2006 MySQL server has gone away。翻译过来就是“MySQL服务器已经跑路了”,你的程序突然就找不到数据库了。更头疼的是,这个错误往往不是立刻出现,而是程序在后台默默运行了几个小时,甚至一两天之后才突然爆发。我印象最深的一次是做一个夜间批量结算的任务,早上起来一看,日志里全是这个错误,结算流程全卡在半路,只能手动去补数据和重启,那叫一个狼狈。
这个错误的核心,其实是数据库连接的生命周期管理问题。你的PHP脚本(也就是队列的worker进程)和MySQL服务器之间建立了一条TCP连接。这条连接不是永恒的,MySQL服务器端有个wait_timeout参数(默认通常是28800秒,也就是8小时),如果这条连接在这么长的时间里没有任何动静,MySQL服务器就会认为它已经“死”了,为了节省资源,会主动关闭它。这时候,你的worker进程还傻傻地以为连接健在,下一次试图执行SQL查询时,就会触发“gone away”错误。在传统的Web请求中,这个问题不明显,因为每次HTTP请求结束,PHP进程通常会退出,连接自然释放。但队列的worker进程是常驻内存的,它会一直活着,处理完一个任务接着处理下一个,这条数据库连接也就一直被占用着,长时间空闲就触发了超时断开。
所以,这根本不是ThinkPHP或者thinkphp-queue的bug,而是一个在长生命周期进程中使用数据库连接时必然会遇到的经典问题。搞清楚了原因,解决起来就有了方向。接下来,我们就深入看看ThinkPHP队列的两种运行模式,以及如何从根本上解决这个断线问题。
2. 理解根源:work模式与listen模式的天壤之别
要治本,得先摸清thinkphp-queue的脾气。它执行队列任务主要有两种命令:queue:work和queue:listen。别看它们最终都能处理任务,但内在机制完全不同,这也直接决定了它们面对数据库断线时的表现。
### 2.1 work模式:一个“长寿”的工人
当你使用 php think queue:work 命令时,你启动的是一个常驻内存的守护进程。这个worker进程会一次性加载完你的所有PHP代码和框架,然后开始循环:从队列(比如Redis)里取一个任务,执行,再取下一个,如此往复。在这个过程中,它创建的数据库连接(如果任务里用到了)会一直保持着,直到这个进程被手动终止。
这种模式的优点是性能极高。因为代码和框架只初始化一次,连接也只建立一次,后续每个任务都省去了大量的初始化开销,速度飞快。我早期为了追求极致的处理速度,在很多项目里都用的这种模式。
但它的致命缺点就是对“MySQL server has gone away”错误毫无抵抗力。因为那个数据库连接从出生就一直活着,如果任务处理不频繁,或者有波谷期,连接很容易空闲超过MySQL的wait_timeout时间,然后就被服务器无情切断。等下一个需要数据库的任务到来时,就会立刻报错。而且,一旦报


1189

被折叠的 条评论
为什么被折叠?



