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
};