PHP 对rabbitMQ的详细使用讲解测试(TP5) 版本二

rabbitMQ 下载官网 https://www.rabbitmq.com/

PHP链接MQ扩展地址 https://github.com/php-amqplib/php-amqplib

或者使用  composer  安装

 composer require php-amqplib/php-amqplib
 

更详细的例子 讲解

 

<?php
/**
 * Created by PhpStorm.
 * User: Json
 * Date: 2020/4/22
 * Time: 14:04
 */
namespace app\index\controller;


use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
use think\Controller;


class Test extends Controller{
    protected $connection;
    protected $channel;

    //初始化程序
    public function initialize(){
        $connection=new AMQPStreamConnection("127.0.0.1",5672,'guest','guest');
        $this->connection=$connection;
        $this->channel=$this->connection->channel();
    }

    //简单的 发送者
    public function send(){
        //要发送 我们必须为我们发送声明一个队列 然后我们可以向队列发送消息
        $this->channel->queue_declare('hello', false, false, false, false);
        $msg=new AMQPMessage("Hello Word");
        $this->channel->basic_publish($msg, '', 'hello');
        echo " [x] Sent 'Hello World!'\n";
        //发送完消息后 关闭通道
        $this->channel->close();
        $this->connection->close();
    }
    //简单的 接收者
    public function  receive(){
        //然后声明我们将要消耗的队列。请注意,这与发送的队列中的队列相匹配。
        $this->channel->queue_declare('hello', false, false, false, false);
        echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";

        //我们要创建一个回调函数来接收发送者发送的消息
        $this->channel->basic_consume('hello', '', false, true, false, false, 'msg');
        // 只要通道注册了回调,就进行循环
        while ($this->channel ->is_consuming()) {
            $this->channel->wait();
        }
    }

    //发送工作消息队列
    public function new_task($argv){

        $data=$argv;
        if(empty($data)){
            $data="Hello Word";
        }
        # 使消息持久化
        //为了防止系统崩溃 所有的消息消失 我们在处理队列和消息的时候 设置持久化

        //要发送 我们必须为我们发送声明一个队列 然后我们可以向队列发送消息
        //非持久化声明队列
        //$this->channel->queue_declare('task_queue', false, false, false, false);
        //为了不让队列消失 首先我们队列声明为持久化 我们可以通过设置 queue_declare方法的第三个参数 设置为 true
        //持久化声明队列
        $this->channel->queue_declare('task_queue', false, true, false, false);
        //尽管这行代码本身不会有错 但是仍然不会正确运行 因为我们已经定义过一个这样的非持久化的队列(队列名要唯一【task_queue】 )
        //这个持久化的声明(queue_declare) 发送者和消费者 声明必须一致 这个地方第三个参数为 true 消费者也需要为true

        //这时候 我们就可以确保MQ在重启之后 queue_declare 队列不会消失
        //另外 我们需要把我们的消息也要设置持久化 设置为 delivery_mode = 2
        $msg=new AMQPMessage($data,array('delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT) );
        $this->channel->basic_publish($msg, '', 'task_queue');
        echo " [x] Sent '",$data,"'\n";
        // 消息持久化 注意:
        //将消息设置为持久化并不能保证不会完全丢失 以上代码告诉了 MQ要把消息存到硬盘,
        //但从RabbitMq收到消息到保存之间还是有一个很小的间隔时间。
        //因为RabbitMq并不是所有的消息都使用fsync(2)——它有可能只是保存到缓存中,并不一定会写到硬盘中。
        //并不能保证真正的持久化,但已经足够应付我们的简单工作队列。如果你一定要保证持久化,
        //你可以使用publisher confirms。  https://www.rabbitmq.com/confirms.html





        //发送完消息后 关闭通道
        $this->channel->close();
        $this->connection->close();
    }
    //接收工作者
    public function  receive_work(){
        //然后声明我们将要消耗的队列。请注意,这与发送的队列中的队列相匹配。
        //非持久化声明队列
      //  $this->channel->queue_declare('task_queue', false, false, false, false);
        //持久化声明队列
        $this->channel->queue_declare('task_queue', false, true, false, false);
        echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";


        //公平调度
        //如果现在有两个接收工作者,处理奇数的工作者比较忙,处理偶数的工作者毕竟闲  然后MQ并不知道这些,
        //他还在一如既往的发消息 这样导致处理数据两位工作者的不公平
        //我们可以使用 basic_qos() 方法 并设置 prefetch_count=1
        //这样是告诉RabbitMQ,再同一时刻,不要发送超过1条消息给一个工作者(worker),
        //直到它已经处理了上一条消息并且作出了响应。这样,RabbitMQ就会把消息分发给下一个空闲的工作者
        // 可多个工作者来执行任务 切不会重复 这个方法
        #只有consumer已经处理并确认了上一条message时queue才分派新的message给它
        $this->channel->basic_qos(null, 1, null);
        //我们要创建一个回调函数来接收发送者发送的消息
        //消息响应默认是开启的。之前的例子中我们可以使用no_ack=True标识把它关闭。是时候设置的第四个参数basic_consume为false
       // (true 意味着不响应ack) ,当工作者(worker)完成了任务,就发送一个响应。
        //$this->channel->basic_consume('task_queue', '', false, true, false, false, 'msgWork');
        $this->channel->basic_consume('task_queue', '', false, false, false, false, 'msgWork');
        //运行上面的代码,我们发现即使使用CTRL+C杀掉了一个工作者(worker)进程,消息也不会丢失。
        //当工作者(worker)挂掉这后,所有没有响应的消息都会重新发送。
        //如果在basic_consume 方法第四个参赛为false 的话 再回调函数里一定要basic_ack




        // 只要通道注册了回调,就进行循环
        while ($this->channel ->is_consuming()) {
            $this->channel->wait();
        }

        $this->channel->close();
        $this->connection->close();
    }


    //发布与订阅
    //分发一个消息给多个消费者 这种模式叫做 “发布/订阅”

    //以搭建一个简单的日志系统 来模拟这种模式
    // 它包括两个程序 第一个程序负责发送日志消息 第二个程序负责获取消息并输出内容
    //在我们这个日志系统中 所有正在运行的接收方程序都会接受到消息
    //我们用 其中一个来把日志写入磁盘 一个来输出到屏幕上

    //最终 日志消息会被广播给所有的接受者

    //需要写入日志的发送者
    public function log_task($argv){
        $data=$argv;
        if(empty($data)){
            $data="Hello Word";
        }
        //交换机
        //交换机 (Exchanges)
        //我们发送消息到队列并从中取出消息。现在是时候介绍RabbitMQ中完整的消息模型了
        //让我们简单的概括一下之前的内容:
        //发布者(producer)是发布消息的应用程序。
        //队列(queue)用于消息存储的缓冲。
        //消费者(consumer)是接收消息的应用程序。

        //MQ消息模型的核心理念是: 发布者不会直接发送任何消息给队列 事实上 发布者甚至不知道消息是否已经被投入到队列
        // 发布者只需要把消息发送给一个交换机(Exchanges)
        // 交换机非常简单  他一边从发布者那边接收消息 一边把消息推送到队列
        // 交换机必须知道如何处理他接收到的消息 是应该推送到指定的队列中还是多个队列中 或者是直接忽略消息
        // 这些规则是可以通过交换机类型(exchange type)来定义的
        // 交换机类型:
        // 直连交换机 (direct)
        // 主题交换机  (topic)
        // 头交换机     (headers)
        // 扇交换机     (fanout)
        // 在这个日志系统中 采用扇交换机类型 从字面意思 应该就能想到 扩散 的一种类型
        // 现在这个场景 我们需要把消息发送给两个接受者 这种类型刚刚合适
        //绑定交换机
        //创建一个 扇交换机 命名为 log_queue
        $this->channel->exchange_declare('log_queue', 'fanout', false, false, false);
        // 查看所有交换机的列表
        // 命令: rabbitmqctl list_exchanges
       // 这个列表中有一些叫做amq.*的交换器。这些都是默认创建的,不过这时候你还不需要使用他们
        //匿名交换机
        //在调用上面写的 四个方法的时候 并没有对交换机进行配置 但仍然可以使用消息发送
        // 因为我们使用了命名为空的 字符串默认了交换机     第二个参数就是交换机的名称
        //$this->channel->basic_publish($msg, '', 'hello');
        // 我们这里使用的默认或者匿名交换机 消息将会根据指定的routing_key 发到指定的队列
        // routing key是basic_publish函数的第三个参数 第二个参数为交换机的名字

        //另外 我们需要把我们的消息也要设置持久化 设置为 delivery_mode = 2
        $msg=new AMQPMessage($data,array('delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT) );
          //现在,我们就可以发送消息到一个我们命名的交换机:
        //我们命名了 交换机 第三个参数就不需要再找寻队列了
        $this->channel->basic_publish($msg, 'log_queue');
        echo " [x] Sent '",$data,"'\n";
        //临时队列
        // 上面的四个方法中 声明了两个队列名 ( hello和task_queue)
        //给队列名称很重要  我们需要把工作者指定到正确的队列
        //如果你打算在发布者(producers)和消费者(consumers)之间共享同队列的话,给队列命名是十分重要的



        //这个方法里的程序 看起来跟上面的四个方法没什么区别  唯一的区别就是把消息发送到了交换机上而不是匿名交换机
        //

        //发送完消息后 关闭通道
        $this->channel->close();
        $this->connection->close();
    }

    //需要工作的接受者
    public function log_work(){

        echo ' [*] Waiting for messages. To exit press CTRL+C', "\n";
        // 创建一个交换机
        $this->channel->exchange_declare('log_queue', 'fanout', false, false, false);
        //1.当我们连接上RabbitMQ的时候,我们需要一个全新的、空的队列。
        //我们可以手动创建一个随机的队列名,或者让服务器为我们选择一个随机的队列名(推荐)。
        //2.当与消费者(consumer)断开连接的时候,这个队列应当被立即删除

        list($queue_name, ,) = $this->channel->queue_declare("", false, false, true, false);
        //  方法返回时,$queue_name变量包含一个随机生成的RabbitMQ队列名称。例如,类似amq.gen-jzty20brgko-hjmujj0wlg。
        //队列交换机绑定  只需工作者队列交换机绑定 发布任务的人 不需要绑定队列 只需要把消息推送的交换机中
        $this->channel->queue_bind($queue_name, 'log_queue');

        //队列交换机绑定(binding)列表查询
        // rabbitmqctl list_bindings

        //我们要创建一个回调函数来接收发送者发送的消息
        //消息响应默认是开启的。之前的例子中我们可以使用no_ack=True标识把它关闭。是时候设置的第四个参数basic_consume为false
        // (true 意味着不响应ack) ,当工作者(worker)完成了任务,就发送一个响应。
        $this->channel->basic_consume($queue_name, '', false, false, false, false, 'msg');
        //运行上面的代码,我们发现即使使用CTRL+C杀掉了一个工作者(worker)进程,消息也不会丢失。
        //当工作者(worker)挂掉这后,所有没有响应的消息都会重新发送。
        //如果在basic_consume 方法第四个参赛为false 的话 再回调函数里一定要basic_ack



        // 只要通道注册了回调,就进行循环
        while ($this->channel ->is_consuming()) {
            $this->channel->wait();
        }

        $this->channel->close();
        $this->connection->close();
    }
    



}

 

 

function msg($msg) {
    echo "[x] Received", $msg->body, "\n";
};
function msgWork($msg){

    echo " [x] Received ", $msg->body, "\n";
    sleep(substr_count($msg->body, '.'));
    echo " [x] Done", "\n";
   // 消息响应默认是开启的。之前的例子中我们可以使用no_ack=True标识把它关闭。
    //是时候设置的第四个参数basic_consume为false (true 意味着不响应ack) ,当工作者(worker)完成了任务,就发送一个响应。
    $msg->delivery_info['channel']->basic_ack($msg->delivery_info['delivery_tag']);
    //一个很容易犯的错误就是忘了basic_ack,后果很严重。
    //消息在你的程序退出之后就会重新发送,
    //如果它不能够释放没响应的消息,RabbitMQ就会占用越来越多的内存。
    //如果在basic_consume 方法第四个参赛为false 的话 再回调函数里一定要basic_ack
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章