很久没更新博客了,最近在下班时间写了一个简单的PHP库——php-async及php-async-manager,以实现PHP中的简单异步回调。
0. 前言
最近遇到个问题,PHP的异步回调怎么实现?
Workman、Swoole、fastcgi_finish_request?
- Workman、Swoole这些框架虽然对异步回调实现的很好,但是如果只需要简单的异步回调就上这些未免大材小用。
- 而fastcgi_finish_request虽然使用方面,但毕竟只能在php-fpm模式下使用,而且一次请求只能使用一次。
之后就一直在思考:能不能自己实现一个PHP可用的简单异步回调呢?最初出现在我脑海的有几个方案:
- 编写PHP扩展,使用C/C++创建守护进程进程,用于监听是否有异步任务到来,如果有任务到来,则执行任务及回调函数。而PHP进程则将异步任务传递给该监听进程,从而实现异步。
- 使用pcntl扩展创建子进程,并让子进程脱离父进程的控制,成为守护进程。子进程用于处理耗时的异步任务,父进程处理HTTP请求及返回响应。
由于第二种方案更简单,因此选择使用第二种方案进行实现。
1. 实现原理及思路
实现原理比较简单:
- 在当前进程中创建子进程,父进程处理耗时较短的HTTP业务逻辑;子进程处理耗时较长的任务,例如邮件发送等。
- 创建子进程后,让子进程脱离父进程的控制,成为守护进程。
- 父进程通过设置
Callable
或Closure
函数,让子进程执行异步任务及回调函数。
每个异步任务都需要两个关键元素组成:异步任务本身、回调函数。所以设计了JobInterface接口,该接口要求实现两个方法:job、callback,分别对应异步任务及回调函数。具体代码为:
interface JobInterface
{
/**
* 异步任务
*
* @return void
*/
public function job();
/**
* 回调函数
*
* @return void
*/
public function callback();
}
2. 遇到的问题及解决方法
在CLI模式下开发运行十分顺利,没遇到什么较大的问题。但是,一切开发完成之后,在php-fpm模式下测试,遇到以下几个问题:
问题
- 创建的子进程由php-fpm管理进程控制,当子进程执行完成后,因为php-fpm没有给该异步进程“收尸”,导致其成为僵尸进程,占用系统资源。
- 因为在php-fpm模式下,进程受php-fpm管理进程的控制,因此猜测异步进程的数量可能受php-fpm配置的进程数限制。
解决方案
- 对于“僵尸进程”的问题,让父进程忽略子进程的结束,从而将子进程的回收权移交给系统的init进程,由init进程回收创建的子进程:
pcntl_signal(SIGCHLD, SIG_IGN);
。 - php-fpm配置的进程数限制,经过测试(将php-fpm配置文件中的最大进程数改为5,并使用静态模式)发现,并未出现猜想中的创建子进程被限制的问题。
3. 最后结果
最后实现的结果为: php-async。在CLI和php-fpm模式下都没有发现问题。
之后,我还基于symfony/console开发了一个用于查看异步任务的命令行工具——php-async-manager,可以用于查看异步任务运行状态、停止正在运行的任务等。
4. 后续拓展
该异步实现方案还存在一个很大的问题,即,每执行一个异步任务就要创建一个进程,当处理的请求数量比较多时,就需要创建太多的进程。
之后了解了NodeJS的异步实现原理,其底层使用libuv库,以事件为驱动,libuv负责将来自操作系统的事件收集起来,或者监视其他来源的事件。这样,用户就可以注册回调函数,回调函数会在事件发生的时候被调用。event-loop会一直保持运行状态。
因此,最好的异步实现方案还是使用C语言开发一个PHP的扩展😂。