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
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章