• RabbitMq学习3-工作队列(Work queues)


    工作队列(又称:任务队列——Task Queues)是为了避免等待一些占用大量资源、时间的操作。当我们把任务(Task)当作消息发送到队列中,一个运行在后台的工作者(worker)进程就会取出任务然后处理。当你运行多个工作者(workers),任务就会在它们之间共享。

    这个概念在网络应用中是非常有用的,它可以在短暂的HTTP请求中处理一些复杂的任务。

    一、准备

      1、使用 sleep()函数来模拟复杂任务情况。我们在字符串中加上点号(.)来表示任务的复杂程度,一个点(.)将会耗时1秒钟。比 如”Hello…”就会耗时3秒钟

      2、生成者

      

    <?php
    
    /**
     * PHP amqp(RabbitMQ) Demo-2
     */
    
    $exchangeName = 'demo';
    $queueName = 'task_queue';
    $routeKey = 'task_queue';
    $message = empty($argv[1]) ? 'Hello World!' : ' '.$argv[1];
    
    $connection = new AMQPConnection(array('host' => '127.0.0.1', 'port' => '5672', 'vhost' => '/', 'login' => 'guest', 'password' => 'guest'));
    $connection->connect() or die("Cannot connect to the broker!
    ");
    
    $channel = new AMQPChannel($connection);
    
    $exchange = new AMQPExchange($channel);
    $exchange->setName($exchangeName);
    
    $queue = new AMQPQueue($channel);
    $queue->setName($queueName);
    $queue->setFlags(AMQP_DURABLE);//声明为持久化
    $queue->declareQueue();
    
    $exchange->publish($message, $routeKey);
    var_dump("[x] Sent $message");
    
    $connection->disconnect();
    View Code

      3、消费者

      

    <?php
    
    /**
     * PHP amqp(RabbitMQ) Demo-2
     */
    $exchangeName = 'demo';
    $queueName = 'task_queue';
    $routeKey = 'task_queue';//路由关键字
    //连接
    $connection = new AMQPConnection(array('host' => '127.0.0.1', 'port' => '5672', 'vhost' => '/', 'login' => 'guest', 'password' => 'guest'));
    $connection->connect() or die("Cannot connect to the broker!
    ");
    //消息通道
    $channel = new AMQPChannel($connection);
    //消息交换机
    $exchange = new AMQPExchange($channel);
    $exchange->setName($exchangeName);
    $exchange->setType(AMQP_EX_TYPE_DIRECT);
    $exchange->declareExchange();
    //消息队列
    $queue = new AMQPQueue($channel);
    $queue->setName($queueName);
    $queue->setFlags(AMQP_DURABLE);//声明为持久化
    $queue->declareQueue();
    $queue->bind($exchangeName, $routeKey);//绑定
    
    var_dump('[*] Waiting for messages. To exit press CTRL+C');  
    while (TRUE) {  
            $queue->consume('callback');
           //告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个工作者(worker),直到它已经处理了上一条消息并且作出了响应
            $channel->qos(0,1);
    }
    $connection->disconnect();
    
    function callback($envelope, $queue) {  
            $msg = $envelope->getBody();
            var_dump(" [x] Received:" . $msg);
            sleep(substr_count($msg,'.'));//计算.在$msg中出现的次数
            //消费者会通过一个ack(响应),告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ就会释放并删除这条消息
            $queue->ack($envelope->getDeliveryTag());
    }
    View Code

    二、轮询分发

      1、默认来说,RabbitMQ会按顺序得把消息发送给每个消费者(consumer)。平均每个消费者都会收到同等数量得消息。这种发送消息得方式叫做——轮询(round-robin)。试着添加两个个或更多得工作者(workers)。

      2、首先,我们先同时运行两个worker.php脚本,它们都会从队列中获取消息,我们需要打开三个终端,两个用来运行worker.php脚本,这两个终端就是我们的两个消费者(consumers)—— C1 和 C2。然后剩下一个终端运行生产者new_task.php脚本,你会发现如果我们发送消息得任务复杂度一样的话,C1和C2会轮询处理消息。

    三、消息响应

      当处理一个比较耗时得任务的时候,消费者(consumers)有时会运行到一半就挂掉。当消息被RabbitMQ发送给 消费者(consumers)之后,马上就会在内存中移除。这种情况,你只要把一个工作者(worker)停止,正在处理的消息就会丢失。同时,所有发送 到这个工作者的还没有处理的消息都会丢失。

    我们不想丢失任何任务消息。如果一个工作者(worker)挂掉了,我们希望任务会重新发送给其他的工作者(worker)。

    为了防止消息丢失,RabbitMQ提供了消息响应(acknowledgments)。消费者会通过一个ack(响应),告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ就会释放并删除这条消息。

    如果消费者(consumer)挂掉了,没有发送响应,RabbitMQ就会认为消息没有被完全处理,然后重新发送给其他消费者(consumer)。这样,即使工作者(workers)偶尔的挂掉,也不会丢失消息。

    function callback($envelope, $queue) {  
        $msg = $envelope->getBody();
        var_dump(" [x] Received:" . $msg);
        sleep(substr_count($msg,'.'));
        $queue->ack($envelope->getDeliveryTag());//消费者发送ack响应
    }
    $queue->consume('callback');
    

    运行上面的代码,我们发现即使使用CTRL+C杀掉了一个工作者(worker)进程,消息也不会丢失。当工作者(worker)挂掉后,所有没有响应的消息都会重新发送。

      

    四、消息持久化

      如果你没有特意告诉RabbitMQ,那么在它退出或者崩溃的时候,它将会流失所有的队列和消息。为了确保信息不会丢失,有两个事情是需要注意的:我们必须把“队列”和“消息”设为持久化。

    首先,为了不让队列丢失,需要把它声明为持久化(durable):

    $queue->setFlags(AMQP_DURABLE);
    

    尽管这行代码本身是正确的,但是仍然不会正确运行。因为我们已经定义过一个叫hello的非持久化队列。RabbitMq不允许你使用不同的参数重新定义一个队列,它会返回一个错误。但我们现在使用一个快捷的解决方法——用不同的名字,例如task_queue。

    $queue->setName('task_queue');
    $queue->setFlags(AMQP_DURABLE);
    $queue->declareQueue();
    

    这个$queue->declareQueue();必须在生产者(producer)和消费者(consumer)对应的代码中修改。

    这时候,我们就可以确保在RabbitMq重启之后queue_declare队列不会丢失。

    五、公平分发

    你应该已经发现,它仍旧没有按照我们期望的那样进行分发。比如有两个工作者(workers),处理奇数消息的比较繁忙,处理偶数消息的比较轻松。然而RabbitMQ并不知道这些,它仍然一如既往的派发消息。

    这时因为RabbitMQ只管分发进入队列的消息,不会关心有多少消费者(consumer)没有作出响应。它盲目的把第n-th条消息发给第n-th个消费者。

    我们可以使用$channel->qos();方法,并设置prefetch_count=1。这样是告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个工作者(worker),直到它已经处理了上一条消息并且作出了响应。这样,RabbitMQ就会把消息分发给下一个空闲的工作者(worker)。

    $channel->qos(0,1);

    原文:https://www.cnblogs.com/grimm/p/5728743.html

    
    
  • 相关阅读:
    python 读取配置文件总是报错 configparser.NoSectionError: No section:
    接口测试面试题(转载)
    pytest文档1--简介及用例规则
    引以为戒,配置ChromeDriver 报错 'module' object is not callable
    截图+存储图片
    UI自动化(selenium+python)之浏览器驱动chromedriver安装和配置
    requests---requests封装请求类型
    工具学习_MarkDown
    python并发编程-多进程与多协程
    python并发编程-多线程同步异步处理
  • 原文地址:https://www.cnblogs.com/ivy-zheng/p/10963896.html
Copyright © 2020-2023  润新知