PHP中的并行处理 - 如何实现?

50

我目前正在尝试在PHP中实现一个作业队列。该队列将作为批处理作业进行处理,并应能够并行处理一些作业。

我已经进行了一些研究,发现了几种实现方法,但我并不真正了解它们的优缺点。

例如,通过多次调用脚本来使用fsockopen进行并行处理,如此处所述:
在PHP中轻松进行并行处理

我找到的另一种方式是使用curl_multi函数。
curl_multi_exec PHP文档

但是,我认为这两种方法会为创建主要运行在后台的队列的批处理增加相当大的开销?

我还阅读了有关pcntl_fork的信息,这似乎也是处理问题的一种方法。但是,如果你不真正知道自己在做什么(就像我现在一样),那看起来会非常混乱。

我还看过Gearman,但在那里,我还需要根据需要动态生成工作线程,而不仅仅运行一些线程,然后让gearman作业服务器将其发送到空闲的工人。特别是因为线程应在执行完一个作业后干净地退出,以免遇到潜在的内存泄漏(代码在该问题上可能不完美)。
Gearman入门

因此,我的问题是,你如何处理PHP中的并行处理?为什么选择你的方法,不同方法可能有哪些优缺点?


选择了Quamis的答案,因为这就是我现在要做的。其他的答案也很好,将来也可以参考他们的方法。再次感谢。 - enricog
有关不同方法的详细讨论,请参见 https://dev59.com/_HVC5IYBdhLWcg3wsTbv#14201579 - Francois Bourgeois
9个回答

23
我使用 exec()。它简单而干净。你基本上需要构建一个线程管理器和线程脚本,以完成你所需的功能。
我不喜欢fsockopen(),因为它会打开一个服务器连接,并且可能会达到Apache的连接限制。
出于同样的原因,我不喜欢curl函数。
我也不喜欢pnctl,因为它需要可用的pnctl扩展,并且你必须跟踪父/子关系。
我从未尝试过gearman......

3
但是你的线程管理器会是什么样子呢?我的主要问题是如何以良好的方式创建这些线程,并在它们完成工作后让它们干净地退出。exec 命令会阻塞,直到命令执行完毕,所以我不能并行运行任务,或者我理解有误吗? - enricog
1
你可以在后台运行东西 :) https://dev59.com/-XVD5IYBdhLWcg3wOo9h - Quamis
1
啊,好的...谢谢,我没想到那个 :) 实际上,在exec php手册的评论中已经有一个示例代码,它也很容易地展示了如何跟踪进程。 - enricog
不过要记住,出于安全原因,有些服务器不允许使用exec()函数。 - Grzegorz
@Gacek:实际上这是PHP的配置特定问题,而不是服务器特定问题。如果您无法访问服务器配置,则很难进行并行处理。 - Quamis

15

好的,我想我们在这里有三个选择:

A. 多线程:

PHP 不支持原生的多线程。但有一个 PHP 扩展(实验性的)叫做 pthreads (https://github.com/krakjoe/pthreads) 可以让你实现多线程。

B. 多进程:

有三种方式可以实现:

  • Forking
  • 执行命令
  • 管道传输

C. 分布式并行处理:

它是如何工作的:

  1. 客户端 应用程序向引擎(MQ 引擎)发送数据(也可以是 JSON 格式的消息)“可以是本地或外部 Web 服务”
  2. MQ 引擎 将数据“大多数情况下存储在内存中,可选地存储在数据库中”存储在队列中(您可以定义队列名称)
  3. 客户端 应用程序请求 MQ 引擎按顺序(FIFO 或基于优先级)处理数据(消息)“您还可以从特定队列请求数据”。


一些 MQ 引擎:

  • ZeroMQ(不错的选择,难以使用) 面向消息的 IPC 库,是 Erlang 中的一个消息队列服务器,在内存中存储作业。它是充当并发框架的套接字库。对于集群产品和超级计算机而言,比 TCP 更快。
  • RabbitMQ(不错的选择,易于使用) 自托管的企业消息队列,不是真正的工作队列,而是可以用作工作队列的消息队列,但需要额外的语义。
  • Beanstalkd(最佳选择,易于使用) (Laravel 内置支持,由 Facebook 构建,用于工作队列) - 有一个非常好的“Beanstalkd 控制台”工具。
  • Gearman
问题:分布式处理的集中式代理系统。
  • Apache ActiveMQ 是Java中最流行的开源消息代理,但存在很多漏洞和问题。
  • Amazon SQS (Laravel内置支持,托管-因此不需要管理。并不真正是工作队列,因此需要额外的工作来处理诸如埋葬工作之类的语义)
  • IronMQ (Laravel内置支持,用Go编写,既可以作为云版本使用,也可以在本地使用)
  • Redis (Laravel内置支持,速度不太快,因为它不是为此而设计的)
  • Sparrow (基于memcache编写的Ruby)
  • Starling (基于memcache编写的Ruby,在Twitter上构建)
  • Kestrel (只是另一种QM)
  • Kafka (在Scala中由LinkedIn编写)
  • EagleMQ 是一个开源、高性能、轻量级的队列管理器(用C编写)
  • 更多信息可在http://queues.io找到。


    pthreads项目已停止,并且无法在PHP 7.4.x上编译 https://github.com/krakjoe/pthreads/issues/929 - Meloman
    1
    PHP 8现在支持多线程。https://www.php.net/parallel - Think Big
    不仅是 PHP 8,PHP 7.2+ 也支持“parallel”。 - T.Todua

    5

    采用本地PHP (7.2+) Parallel 库,即:

    use \parallel\Runtime;
    
    $sampleFunc = function($num, $param2, $param3) { 
        echo "[Start: $num]";  
        sleep(rand(1,3) ); 
        echo "[end:$num]";   
    };
    
    for($i = 0; $i < 11; $i++) { 
        \parallel\run($sampleFunc,  [$param1=$i, $param2=null, $param3="blabla"] );
    }
    for ($i = 0; $i < 11; $i++) {
        echo " <REGULAR_CODE> ";
        sleep(1);
    }
    

    (顺便说一下,您需要经历一条艰难的道路才能安装支持 ZTS 的 PHP 并启用 parallel。我建议使用 phpbrew 来完成这项任务。)

    4
    以下是几种PHP并行处理的选项概述。

    AMP

    请查看Amp - 简化异步并发 - 这似乎是我见过的最成熟的PHP库,用于并行处理。

    Peec's Process Class

    这个类发布在PHP的exec()函数的评论中,为创建新进程和跟踪它们提供了一个真正简单的起点。

    例子:

    // You may use status(), start(), and stop(). notice that start() method gets called automatically one time.
    $process = new Process('ls -al');
    
    // or if you got the pid, however here only the status() metod will work.
    $process = new Process();
    $process.setPid(my_pid);
    
    // Then you can start/stop/check status of the job.
    $process.stop();
    $process.start();
    if ($process.status()) {
        echo "The process is currently running";
    } else {
        echo "The process is not running.";
    }
    

    其他方案比较

    还有一篇很棒的文章在PHP中进行异步处理或多任务处理,解释了各种方法的优缺点:

    Doorman

    然后,还有这个简单的教程,并被封装成一个小库叫做Doorman

    希望这些链接为更多研究提供一个有用的起点。


    4
    首先,这个答案是基于Linux操作系统环境的。另一个pecl扩展是parallel,你可以通过运行pecl install parallel来安装它,但它有一些先决条件:
    1. 安装ZTS(Zend线程安全)构建PHP 7.2+版本
    2. 如果您通过源代码构建此扩展,请检查php.ini类似的配置文件,然后将extension=parallel.so添加到其中。
    然后查看完整示例gist:https://gist.github.com/krakjoe/0ee02b887288720d9b785c9f947f3a0a 或php官方网站url:https://www.php.net/manual/en/book.parallel.php

    一个简单的教程:https://harry.plus/blog/install-php-7-4-zts-with-ext-parallel-in-ubuntu/ - ZalemCitizen

    4

    如果您的应用程序将在Unix/Linux环境下运行,建议您采用分叉选项。这基本上很容易实现。我曾经用它来管理Cron并编写代码,以便在无法使用分叉选项时可以恢复到Windows友好的代码路径。

    多次运行整个脚本的选项确实会增加相当大的开销,正如您所说。如果您的脚本很小,这可能不是问题。但是,您可能会习惯于通过您选择的方式在PHP中进行并行处理。下一次,如果您有一个使用200MB数据的作业,这可能非常困难。因此,最好学习一种适合您的方法。

    我还测试了Gearman,我非常喜欢它。有一些要考虑的事情,但总体而言,它提供了一种非常好的方式,可以将作业分发到运行不同语言编写的不同应用程序的不同服务器上。除了设置它之外,实际上从PHP或任何其他语言内部使用它都非常简单。

    对于您需要做的事情来说,这可能是过度杀伤。但是,它将为您打开处理数据和作业的新可能性,因此我建议您尝试Gearman。


    2

    我更喜欢使用exec()和gearman。 exec()简单易用,不需要连接,占用的内存较少。 gearman需要建立套接字连接,工作进程会占用一些内存。 但是gearman比exec()更加灵活和快速。最重要的是,它可以在其他服务器上部署工作进程。如果工作需要耗费时间和资源,那么我会在我的当前项目中使用gearman。


    2
    我使用PHP的pnctl - 只要您知道自己在做什么,它就很好用。我理解您的情况,但我认为理解我们的代码并不难,我们只需要在实现作业队列或并行处理时更加谨慎即可。
    我觉得只要您编写得完美,并确保流程完美,当您实现时应该牢记并行处理。
    您可能会犯以下错误:
    1. 循环-应该能够通过全局变量处理。
    2. 处理一些交易集-同样,只要您正确定义了这些集合,就应该能够完成。
    请看这个例子-https://github.com/rakesh-sankar/Tools/blob/master/PHP/fork-parallel-process.php
    希望对您有所帮助。

    1

    在“PHP中的简单并行处理”中描述的方法真是太可怕了——原则上没问题,但实现方式?正如你已经指出的那样,curl_multi_函数提供了更好的实现方式。

    但我认为这两种方法会增加很多开销

    是的,你可能不需要客户端和服务器HTTP堆栈来处理任务,但除非你为Google工作,否则你的开发时间比硬件成本更昂贵,并且有很多工具可以管理HTTP/分析性能,而且有一个定义良好的标准涵盖状态通知和身份验证等内容。

    你实现解决方案的方式很大程度上取决于你需要的事务完整性级别以及是否需要按顺序处理。

    在你提到的方法中,我建议专注于使用curl_multi_的HTTP请求方法。但如果你需要良好的事务控制/按顺序交付,则一定要在消息源和处理代理之间运行代理守护程序(这里有一个适用于代理的单线程服务器框架here)。请注意,处理代理应一次只处理一条消息。

    如果您需要高度可扩展的解决方案,那么请看一下适当的消息队列系统,例如RabbitMQ
    希望对您有所帮助。
    C.

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接