• RabbitMQ中文文档PHP版本(二)--发布/订阅


    2019年12月10日10:01:00

    原文:https://www.rabbitmq.com/tutorials/tutorial-three-php.html

    工作队列

    (使用php-amqplib

    先决条件

    本教程假定RabbitMQ 在标准端口(5672)的本地主机安装并运行如果您使用其他主机,端口或凭据,则连接设置需要进行调整。

    在哪里获得帮助

    如果您在阅读本教程时遇到困难,可以 通过邮件列表与我们联系。

    在第一个教程中,我们编写了程序来发送和接收来自命名队列的消息。在这一部分中,我们将创建一个工作队列,该队列将用于在多个工作人员之间分配耗时的任务。

    工作队列(又称任务队列的主要思想是避免立即执行资源密集型任务,而不得不等待它完成。相反,我们安排任务在以后完成。我们将任务封装 为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当您运行许多工作人员时,任务将在他们之间共享。

    这个概念在Web应用程序中特别有用,因为在Web应用程序中,不可能在较短的HTTP请求窗口内处理复杂的任务。

    制备

    在本教程的上半部分,我们发送了一条包含“ Hello World!”的消息。现在,我们将发送代表复杂任务的字符串。我们没有现实世界的任务,例如要调整大小的图像或要渲染的pdf文件,所以我们假装自己很忙-使用sleep()函数来伪造它我们将字符串中的点数作为它的复杂度。每个点将占“工作”的一秒。例如,Hello ...描述的虚假任务 将花费三秒钟。

    我们将稍微修改上一个示例中send.php代码,以允许从命令行发送任意消息。该程序会将任务安排到我们的工作队列中,因此将其命名为 new_task.php

    $data = implode(' ', array_slice($argv, 1));
    if (empty($data)) {
        $data = "Hello World!";
    }
    $msg = new AMQPMessage($data);
    
    $channel->basic_publish($msg, '', 'hello');
    
    echo ' [x] Sent ', $data, "
    ";

    我们旧的receive.php脚本也需要进行一些更改:它需要为消息正文中的每个点伪造一秒钟的工作。它会从队列中弹出消息并执行任务,因此我们将其称为worker.php

    $callback = function ($msg) {
      echo ' [x] Received ', $msg->body, "
    ";
      sleep(substr_count($msg->body, '.'));
      echo " [x] Done
    ";
    };
    
    $channel->basic_consume('hello', '', false, true, false, false, $callback);

    请注意,我们的假任务模拟执行时间。

    按照教程一运行它们:

    # shell 1
    php worker.php
    # shell 2
    php new_task.php "A very hard task which takes two seconds.."

    循环调度

    使用任务队列的优点之一是能够轻松并行化工作。如果我们正在积压工作,我们可以增加更多的工人,这样就可以轻松扩展。

    首先,让我们尝试同时运行两个worker.php脚本。他们俩都将从队列中获取消息,但是究竟如何呢?让我们来看看。

    您需要打开三个控制台。两个将运行worker.php 脚本。这些游戏机将成为我们的两个使用者-C1和C2。

    # shell 1
    php worker.php
    # => [*] Waiting for messages. To exit press CTRL+C
    # shell 2
    php worker.php
    # => [*] Waiting for messages. To exit press CTRL+C

    在第三篇中,我们将发布新任务。启动使用者之后,您可以发布一些消息:

    # shell 3
    php new_task.php First message.
    php new_task.php Second message..
    php new_task.php Third message...
    php new_task.php Fourth message....
    php new_task.php Fifth message.....

    让我们看看交付给我们工人的东西:

    # shell 1
    php worker.php
    # => [*] Waiting for messages. To exit press CTRL+C
    # => [x] Received 'First message.'
    # => [x] Received 'Third message...'
    # => [x] Received 'Fifth message.....'
    
    # shell 2
    php worker.php
    # => [*] Waiting for messages. To exit press CTRL+C
    # => [x] Received 'Second message..'
    # => [x] Received 'Fourth message....'

    默认情况下,RabbitMQ将每个消息依次发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。与三个或更多的工人一起尝试。

    消息确认

    执行任务可能需要几秒钟。您可能想知道,如果其中一个使用者开始一项漫长的任务而仅部分完成而死掉,会发生什么情况。使用我们当前的代码,RabbitMQ一旦向消费者发送了一条消息,便立即将其标记为删除。在这种情况下,如果您杀死一个工人,我们将丢失正在处理的消息。我们还将丢失所有发送给该特定工作人员但尚未处理的消息。

    但是我们不想丢失任何任务。如果一个工人死亡,我们希望将任务交付给另一个工人。

    为了确保消息永不丢失,RabbitMQ支持 消息确认消费者发送回一个确认(告知),告知RabbitMQ特定的消息已被接收,处理,并且RabbitMQ可以自由删除它。

    如果使用者死了(其通道已关闭,连接已关闭或TCP连接丢失)而没有发送确认,RabbitMQ将了解消息未完全处理,并将重新排队。如果同时有其他消费者在线,它将很快将其重新分发给另一个消费者。这样,您可以确保即使工人偶尔死亡也不会丢失任何消息。

    没有任何消息超时;消费者死亡时,RabbitMQ将重新传递消息。即使处理一条消息花费非常非常长的时间也没关系。

    消息确认默认为关闭。现在是时候通过将第四个参数basic_consume设置false来打开它们了 (true表示没有ack),并在完成任务后从工作人员发送适当的确认。

    $callback = function ($msg) {
      echo ' [x] Received ', $msg->body, "
    ";
      sleep(substr_count($msg->body, '.'));
      echo " [x] Done
    ";
      $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
    };
    
    $channel->basic_consume('task_queue', '', false, false, false, false, $callback);

    使用此代码,我们可以确保,即使您在处理消息时使用CTRL + C杀死工作人员,也不会丢失任何信息。工人死亡后不久,所有未确认的消息将重新发送。

    确认必须在收到交货的同一通道上发送。尝试使用其他通道进行确认将导致通道级协议异常。请参阅有关确认文档指南 以了解更多信息。

    被遗忘的确认

    错过ack是一个普遍的错误这是一个简单的错误,但是后果很严重。当您的客户端退出时,消息将被重新发送(可能看起来像是随机重新发送),但是RabbitMQ将消耗越来越多的内存,因为它将无法释放任何未确认的消息。

    为了调试这种错误,您可以使用rabbitmqctl 打印messages_unacknowledged字段:

    sudo rabbitmqctl list_queues名称messages_ready messages_unacknowledged
    

    在Windows上,删除sudo:

    rabbitmqctl.bat list_queues名称messages_ready messages_unacknowledged
    

    讯息持久性

    我们已经学会了如何确保即使消费者死亡,任务也不会丢失。但是,如果RabbitMQ服务器停止,我们的任务仍然会丢失。

    RabbitMQ退出或崩溃时,它将忘记队列和消息,除非您告知不要这样做。要确保消息不会丢失,需要做两件事:我们需要将队列和消息都标记为持久。

    首先,我们需要确保RabbitMQ永远不会丢失我们的队列。为此,我们需要将其声明为持久的为此,我们将第三个参数作为true传递给queue_declare

    $channel->queue_declare('hello', false, true, false, false);

    尽管此命令本身是正确的,但在我们当前的设置中将无法使用。这是因为我们已经定义了一个名为hello的队列 ,该队列并不持久。RabbitMQ不允许您使用不同的参数重新定义现有队列,并且将向尝试执行此操作的任何程序返回错误。但是有一个快速的解决方法-让我们声明一个名称不同的队列,例如task_queue

    $channel->queue_declare('task_queue', false, true, false, false);

    设置为true的此标志需要同时应用于生产者代码和消费者代码。

    在这一点上,我们确保即使RabbitMQ重新启动task_queue队列也不会丢失。现在,我们需要将消息标记为持久消息-通过设置delivery_mode = 2 message属性,AMQPMessage将其作为属性数组的一部分。

    $msg = new AMQPMessage(
        $data,
        array('delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT)
    );

    关于消息持久性的说明

    将消息标记为持久性并不能完全保证不会丢失消息。尽管它告诉RabbitMQ将消息保存到磁盘,但是RabbitMQ接受消息并且尚未保存消息时,还有很短的时间。而且,RabbitMQ不会对每条消息都执行fsync(2)-它可能只是保存到缓存中,而没有真正写入磁盘。持久性保证并不强,但是对于我们的简单任务队列而言,这已经绰绰有余了。如果您需要更强有力的保证,则可以使用 发布者确认

    公平派遣

    您可能已经注意到,调度仍然无法完全按照我们的要求进行。例如,在有两名工人的情况下,当所有奇怪的消息都很重,甚至消息很轻时,一位工人将一直忙碌而另一位工人将几乎不做任何工作。好吧,RabbitMQ对此一无所知,并且仍将平均分配消息。

    发生这种情况是因为RabbitMQ在消息进入队列时才调度消息。它不会查看使用者的未确认消息数。它只是盲目地将每第n条消息发送给第n个使用者。

    为了克服这一点,我们可以将basic_qos方法与 prefetch_count = 1设置一起使用。这告诉RabbitMQ一次不要给工人一个以上的消息。换句话说,在处理并确认上一条消息之前,不要将新消息发送给工作人员。而是将其分派给不忙的下一个工作程序。

    $channel->basic_qos(null, 1, null);

    关于队列大小的注意事项

    如果所有工作人员都忙,您的队列就满了。您将需要留意这一点,也许会增加更多的工作人员,或者有其他一些策略。

    放在一起

    我们的new_task.php文件的最终代码

    <?php
    
    require_once __DIR__ . '/vendor/autoload.php';
    use PhpAmqpLibConnectionAMQPStreamConnection;
    use PhpAmqpLibMessageAMQPMessage;
    
    $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
    $channel = $connection->channel();
    
    $channel->queue_declare('task_queue', false, true, false, false);
    
    $data = implode(' ', array_slice($argv, 1));
    if (empty($data)) {
        $data = "Hello World!";
    }
    $msg = new AMQPMessage(
        $data,
        array('delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT)
    );
    
    $channel->basic_publish($msg, '', 'task_queue');
    
    echo ' [x] Sent ', $data, "
    ";
    
    $channel->close();
    $connection->close();

    (new_task.php源代码)

    还有我们的worker.php

    <?php
    
    require_once __DIR__ . '/vendor/autoload.php';
    use PhpAmqpLibConnectionAMQPStreamConnection;
    
    $connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
    $channel = $connection->channel();
    
    $channel->queue_declare('task_queue', false, true, false, false);
    
    echo " [*] Waiting for messages. To exit press CTRL+C
    ";
    
    $callback = function ($msg) {
        echo ' [x] Received ', $msg->body, "
    ";
        sleep(substr_count($msg->body, '.'));
        echo " [x] Done
    ";
        $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
    };
    
    $channel->basic_qos(null, 1, null);
    $channel->basic_consume('task_queue', '', false, false, false, false, $callback);
    
    while ($channel->is_consuming()) {
        $channel->wait();
    }
    
    $channel->close();
    $connection->close();

    (worker.php源代码)

    使用消息确认和预取,您可以设置工作队列。耐用性选项即使重新启动RabbitMQ也可以使任务继续存在。

  • 相关阅读:
    barabasilab-networkScience学习笔记2-图理论
    barabasilab-networkScience学习笔记1-网络科学简介
    windows下R语言在终端的运行
    远程打印服务器
    矩震级Mw与地震矩M0的换算关系
    关于地震科学台阵数据中心的仪器记录值介绍
    capjoint中的tel3核心代码teleseis3.f90
    Centos7安装
    matlab中hold on 和hold off功能的区别
    sac cut
  • 原文地址:https://www.cnblogs.com/zx-admin/p/12014964.html
Copyright © 2020-2023  润新知