使用PHP进行异步处理-每个作业一个工人

使用PHP进行异步处理-每个作业一个工人,php,asynchronous,message-queue,task-queue,gearman,Php,Asynchronous,Message Queue,Task Queue,Gearman,考虑一个PHP web应用程序,其目的是接受用户启动通用异步作业的请求,然后创建一个工作进程/线程来运行该作业。这些作业并不特别占用CPU或内存,但预计会经常阻塞I/O调用。每秒启动的作业不应超过一个或两个,但由于运行时间较长,可能会同时运行多个作业 因此,作业并行运行至关重要。此外,每个作业都必须由负责杀死挂起的工作进程、根据用户请求中止工作进程等的manager守护进程进行监视 实施这样一个系统的最佳方式是什么?我可以看到: 从经理那里分流一名员工——这似乎是最低级别的选择,我必须自己实施一

考虑一个PHP web应用程序,其目的是接受用户启动通用异步作业的请求,然后创建一个工作进程/线程来运行该作业。这些作业并不特别占用CPU或内存,但预计会经常阻塞I/O调用。每秒启动的作业不应超过一个或两个,但由于运行时间较长,可能会同时运行多个作业

因此,作业并行运行至关重要。此外,每个作业都必须由负责杀死挂起的工作进程、根据用户请求中止工作进程等的manager守护进程进行监视

实施这样一个系统的最佳方式是什么?我可以看到:

  • 从经理那里分流一名员工——这似乎是最低级别的选择,我必须自己实施一个监控系统。Apache是web服务器,因此该选项似乎需要通过FastCGI启动任何PHP工作程序
  • 使用某种作业/消息队列。(gearman、beanstalkd、RabbitMQ等)-起初,这似乎是显而易见的选择。经过一些研究,我对所有的选择都有些困惑。例如,Gearman看起来像是为大型分布式系统设计的,在这些系统中有一个固定的工人池……所以我不知道它是否适合我所需要的(每个工作一个工人)

  • 好吧,如果你在Linux上,你可以用它来打发孩子们。然后“主人”看着孩子们。每个子级完成其任务,然后正常存在

    就我个人而言,在我的实现中,我从来都不需要消息队列。我只是在“master”中使用了一个带锁的数组。当一个孩子得到一份工作时,他会写一个带有工作id号的锁文件。然后,主人会等到孩子离开。如果子项退出后锁文件仍然存在,则我知道任务未完成,并使用相同的作业重新启动子项(删除锁文件后)。根据您的情况,您可以在一个简单的数据库表中实现队列。在表中插入作业,并每隔30秒或60秒检查主控中的表以查找新作业。然后,仅在子项完成后(并且子项删除了锁文件)才从表中删除它们。如果一次运行多个“master”,则会出现问题,但可以实现全局“master pid文件”来检测和防止多个实例

    我不建议使用FastCGI。它可能会导致一些非常模糊的问题,因为环境注定会持续存在。相反,如果必须使用CGI web界面,请使用CGI,但最好使用CLI应用程序(deamon)。要从其他进程与主进程接口,可以使用套接字进行TCP通信,也可以创建用于通信的套接字


    至于检测挂起的工作人员,您可以实现一个“心跳”系统,在该系统中,孩子每隔这么多秒向主进程发出一个
    SIG_USR1
    。那么如果你有两三次没有收到孩子的来信,它可能会被挂起来。但问题是,由于PHP不是多线程的,所以您无法判断子线程是否挂起,或者它是否只是在等待阻塞资源(如数据库调用)。。。至于实现“心跳”,您可以使用自动化心跳(但请记住,阻止调用仍然不会执行).

    workerpool可能很有趣:


    当您使用pcntl_fork运行异步一个任务和多个作业时,或者您将每隔(s)秒创建一次持久性查询,小心cpu消耗高,您可能会得到挂起的处理内存,因为无法再次分配内存,我认为您可以使用Gearman完全构建的最佳选择,或者您也可以尝试使用IronWorker之类的云计算工具。

    谢谢。我已经做过几次了,效果非常好。嗯,我应该说,如果您的用例符合系统的限制(IPC相当昂贵,等等),那么它工作得非常好。如果它们没有很好地对齐,您应该使用真正的线程实现和PHP以外的语言…但是要小心使用
    pcntl_fork()
    。我曾经遇到过在父进程和子进程之间以奇怪的方式共享数据库连接的问题。如果一些PECL扩展有类似的怪癖,我也不会感到惊讶。我会避开PHP中的分叉,并通过
    exec()
    等生成单独的进程,只是为了保持简单,我在分叉后显式地重新打开子进程中的所有连接。叉子没什么好怕的(我经常用它)。但是这是一个反复试验的过程,因为没有很多关于这个主题的文档。通过
    exec
    执行的问题是,它使通信和监视变得更加困难(因为对于一个
    exec
    是阻塞的,而对于两个
    exec
    非阻塞调用(末尾附加了
    的调用))的进程id要难得多…关于FastCGI的有趣评论。我不知道,但这是有道理的。对于这个特定的应用程序,没有理由说master/manager不能成为CLI守护进程,所以我可以尝试这种方法。