管理队列处理器进程和内存泄露问题
引入 Supervisor
一个队列 Worker 是一个预期会无限期运行的 PHP 进程,不过遇到以下情况,会异常退出:
- 触及内存限制
- 任务执行超时
- 失去队列驱动连接(数据库、Redis 等)
- 进程崩溃
如果你使用了监控工具,在进程异常崩溃时会收到通知,然后我们需要手动去重启进程,这不是最好的解决方案。
我们可以使用一些开源的进程管理工具管理队列处理器进程的启动、监控和退出重启。这其中最流行的就是 Supervisor,我们可以在 Ubuntu 上这样安装和启动它:
sudo apt install supervisor
sudo service supervisor restart
然后配置 Supervisor 启动和监控 4 个队列处理器进程:
这些配置位于 /etc/supervisor/conf.d/notification-workers.conf
中。我们来简单看看每个配置项的含义:
command
和numprocs
:分别用于定义进程启动命令和进程的数量;autostart
、autorestart
和user
:每个进程的自动启动、自动重启和用户名;stopasgroup
:通过 Supervisor 停止进程会向组内每个进程发送信号;stopwaitsecs
:Supervisor 发送停止信号和强制停止的时间间隔,即在此时间内没有停止,则强制停止它。
启动 Worker
创建好上述配置文件后,就可以通过 Supervisor 启动队列 Worker 进程了:
这样一来,就会通过 Supervisor 启动 4 个队列处理器进程,如果中途有进程退出,Supervisor 会自动重启它。
停止 Worker
有时候,需要主动停止队列处理器进程,这可以通过如下 Supervisor 命令实现:
或者停止所有处理器进程:
这种方式会发送信号给处理器进程,让其完成当前正在执行的任务后停止获取新的队列任务并退出,所以是一种优雅的退出机制。
按照上面的配置,Supervisor 会给每个处理器进程 3600s 用于处理完手动的任务然后退出,你应该将 stopwaitsecs
的值设置为比耗时最长的任务处理时间还要长的值。
这样一来,当处理器进程收到来自 Supervisor 的停止信号,就能够从容运行完当前正在执行的任务然后优雅地退出,而不是等到 Supervisor 来强制终止。
此外,Supervisor 不会自动启动所有通过 supervisorctl stop
停止的进程,要再次启动它们,需要再次运行 supervisorctl start
:
重启 Worker
队列 Worker 作为常驻内存的进程,意味着应用代码的调整不会影响已经启动的队列处理器进程,要让这些代码修改在队列处理的时候生效,需要重启这些进程:
避免内存泄露
关于 PHP 是否适用于编写常驻内存程序长期以来一直是个争论不休的问题,据我在包含数百个处理器进程的大型项目中的使用经验,我认为 PHP 是可以胜任的。
处理器进程启动的时候,会启动一个完整的 Laravel 控制台应用,然后让其在后台处理所有队列任务,我已经见识过 Laravel 处理器进程的强大,它们可以在不同配置的服务器上处理非常多的工作,而且完成的非常好。
尽管如此,如何避免内存泄露还是很有挑战性的,随着时间的推移,一些引用会堆积在服务器内存中,这些引用不会被 PHP 检测到,并在某些时候导致服务器崩溃。
解决方案也是非常简单,大多数时候重启处理器进程就好了。
有了像 Supervisor 这样的进程管理器,你可以每小时重启一次处理器进程来清理内存,非常方便,这可以通过定义一个 CRON 任务调度来实现:
如果不使用 CRON 任务,还可以通过 --max-jobs
和 --max-time
来限制队列处理器进程处理的任务上限和时间上限:
这样一来,处理器进程在处理完 1000 个任务或者 1 小时后会自动退出。
处理器进程会在每次处理完任务后检查是否达到退出条件,因此不会在任务处过程中退出。
最后,你还可以在队列任务类处理方法中通过信号显式告知处理器进程退出:
这种方式在你知道某个队列任何很耗内存时很有用,你可以在每次运行完重启一次进程以便及时释放内存。
定时重启队列处理器进程是避免 PHP 进程内存泄露非常有效的方法。
No Comments