關於消息隊列,從前年開始斷斷續續看了些資料,想寫很久了,但一直沒騰出空,近來分別碰到幾個朋友聊這塊的技術選型,是時候把這塊的知識整理記錄一下了。
市面上的消息隊列產品有很多,比如老牌的 ActiveMQ、RabbitMQ ,目前我看最火的 Kafka ,還有 ZeroMQ ,去年底阿里巴巴捐贈給 Apache 的 RocketMQ ,連 redis 這樣的 NoSQL 數據庫也支持 MQ 功能。總之這塊知名的產品就有十幾種,就我自己的使用經驗和興趣只打算談談 RabbitMQ、Kafka 和 ActiveMQ ,本文先講 RabbitMQ ,在此之前先看下消息隊列的相關概念。
什麼叫消息隊列
消息(Message)是指在應用間傳送的數據。消息可以非常簡單,比如只包含文本字符串,也可以更復雜,可能包含嵌入對象。
消息隊列(Message Queue)是一種應用間的通信方式,消息發送後可以立即返回,由消息系統來確保消息的可靠傳遞。消息發佈者只管把消息發佈到 MQ 中而不用管誰來取,消息使用者只管從 MQ 中取消息而不管是誰發佈的。這樣發佈者和使用者都不用知道對方的存在。
爲何用消息隊列
從上面的描述中可以看出消息隊列是一種應用間的異步協作機制,那什麼時候需要使用 MQ 呢?
以常見的訂單系統爲例,用戶點擊【下單】按鈕之後的業務邏輯可能包括:扣減庫存、生成相應單據、發紅包、發短信通知。在業務發展初期這些邏輯可能放在一起同步執行,隨着業務的發展訂單量增長,需要提升系統服務的性能,這時可以將一些不需要立即生效的操作拆分出來異步執行,比如發放紅包、發短信通知等。這種場景下就可以用 MQ ,在下單的主流程(比如扣減庫存、生成相應單據)完成之後發送一條消息到 MQ 讓主流程快速完結,而由另外的單獨線程拉取MQ的消息(或者由 MQ 推送消息),當發現 MQ 中有發紅包或發短信之類的消息時,執行相應的業務邏輯。
以上是用於業務解耦的情況,其它常見場景包括最終一致性、廣播、錯峯流控等等。
1.什麼是MQ
消息隊列(Message Queue,簡稱MQ),從字面意思上看,本質是個隊列,FIFO先入先出,只不過隊列中存放的內容是message而已。
其主要用途:不同進程Process/線程Thread之間通信。
爲什麼會產生消息隊列?有幾個原因:
不同進程(process)之間傳遞消息時,兩個進程之間耦合程度過高,改動一個進程,引發必須修改另一個進程,爲了隔離這兩個進程,在兩進程間抽離出一層(一個模塊),所有兩進程之間傳遞的消息,都必須通過消息隊列來傳遞,單獨修改某一個進程,不會影響另一個;
不同進程(process)之間傳遞消息時,爲了實現標準化,將消息的格式規範化了,並且,某一個進程接受的消息太多,一下子無法處理完,並且也有先後順序,必須對收到的消息進行排隊,因此誕生了事實上的消息隊列;
關於消息隊列的詳細介紹請參閱:
《Java帝國之消息隊列》
《一個故事告訴你什麼是消息隊列》
《到底什麼時候該使用MQ》
MQ框架非常之多,比較流行的有RabbitMq、ActiveMq、ZeroMq、kafka,以及阿里開源的RocketMQ。本文主要介紹RabbitMq。
本教程pdf及代碼下載地址:
代碼:https://download.csdn.net/download/zpcandzhj/10585077
教程:https://download.csdn.net/download/zpcandzhj/10585092
2.RabbitMQ
開發語言:Erlang – 面向併發的編程語言。
2.1.1.AMQP
AMQP是消息隊列的一個協議。
RabbitMQ 特點
RabbitMQ 是一個由 Erlang 語言開發的 AMQP 的開源實現。
AMQP :Advanced Message Queue,高級消息隊列協議。它是應用層協議的一個開放標準,爲面向消息的中間件設計,基於此協議的客戶端與消息中間件可傳遞消息,並不受產品、開發語言等條件的限制。
RabbitMQ 最初起源於金融系統,用於在分佈式系統中存儲轉發消息,在易用性、擴展性、高可用性等方面表現不俗。具體特點包括:
-
可靠性(Reliability)
RabbitMQ 使用一些機制來保證可靠性,如持久化、傳輸確認、發佈確認。 -
靈活的路由(Flexible Routing)
在消息進入隊列之前,通過 Exchange 來路由消息的。對於典型的路由功能,RabbitMQ 已經提供了一些內置的 Exchange 來實現。針對更復雜的路由功能,可以將多個 Exchange 綁定在一起,也通過插件機制實現自己的 Exchange 。 -
消息集羣(Clustering)
多個 RabbitMQ 服務器可以組成一個集羣,形成一個邏輯 Broker 。 -
高可用(Highly Available Queues)
隊列可以在集羣中的機器上進行鏡像,使得在部分節點出問題的情況下隊列仍然可用。 -
多種協議(Multi-protocol)
RabbitMQ 支持多種消息隊列協議,比如 STOMP、MQTT 等等。 -
多語言客戶端(Many Clients)
RabbitMQ 幾乎支持所有常用語言,比如 Java、.NET、Ruby 等等。 -
管理界面(Management UI)
RabbitMQ 提供了一個易用的用戶界面,使得用戶可以監控和管理消息 Broker 的許多方面。 -
跟蹤機制(Tracing)
如果消息異常,RabbitMQ 提供了消息跟蹤機制,使用者可以找出發生了什麼。 -
插件機制(Plugin System)
RabbitMQ 提供了許多插件,來從多方面進行擴展,也可以編寫自己的插件。
RabbitMQ 中的概念模型
消息模型
所有 MQ 產品從模型抽象上來說都是一樣的過程:
消費者(consumer)訂閱某個隊列。生產者(producer)創建消息,然後發佈到隊列(queue)中,最後將消息發送到監聽的消費者。
RabbitMQ 基本概念
上面只是最簡單抽象的描述,具體到 RabbitMQ 則有更詳細的概念需要解釋。上面介紹過 RabbitMQ 是 AMQP 協議的一個開源實現,所以其內部實際上也是 AMQP 中的基本概念:
- Message
消息,消息是不具名的,它由消息頭和消息體組成。消息體是不透明的,而消息頭則由一系列的可選屬性組成,這些屬性包括routing-key(路由鍵)、priority(相對於其他消息的優先權)、delivery-mode(指出該消息可能需要持久性存儲)等。 - Publisher
消息的生產者,也是一個向交換器發佈消息的客戶端應用程序。 - Exchange
交換器,用來接收生產者發送的消息並將這些消息路由給服務器中的隊列。 - Binding
綁定,用於消息隊列和交換器之間的關聯。一個綁定就是基於路由鍵將交換器和消息隊列連接起來的路由規則,所以可以將交換器理解成一個由綁定構成的路由表。 - Queue
消息隊列,用來保存消息直到發送給消費者。它是消息的容器,也是消息的終點。一個消息可投入一個或多個隊列。消息一直在隊列裏面,等待消費者連接到這個隊列將其取走。 - Connection
網絡連接,比如一個TCP連接。 - Channel
信道,多路複用連接中的一條獨立的雙向數據流通道。信道是建立在真實的TCP連接內地虛擬連接,AMQP 命令都是通過信道發出去的,不管是發佈消息、訂閱隊列還是接收消息,這些動作都是通過信道完成。因爲對於操作系統來說建立和銷燬 TCP 都是非常昂貴的開銷,所以引入了信道的概念,以複用一條 TCP 連接。 - Consumer
消息的消費者,表示一個從消息隊列中取得消息的客戶端應用程序。 - Virtual Host
虛擬主機,表示一批交換器、消息隊列和相關對象。虛擬主機是共享相同的身份認證和加密環境的獨立服務器域。每個 vhost 本質上就是一個 mini 版的 RabbitMQ 服務器,擁有自己的隊列、交換器、綁定和權限機制。vhost 是 AMQP 概念的基礎,必須在連接時指定,RabbitMQ 默認的 vhost 是 / 。 - Broker
表示消息隊列服務器實體。
AMQP 中的消息路由
AMQP 中消息的路由過程和 Java 開發者熟悉的 JMS 存在一些差別,AMQP 中增加了 Exchange 和 Binding 的角色。生產者把消息發佈到 Exchange 上,消息最終到達隊列並被消費者接收,而 Binding 決定交換器的消息應該發送到那個隊列。
Exchange 類型
Exchange分發消息時根據類型的不同分發策略有區別,目前共四種類型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由鍵,此外 headers 交換器和 direct 交換器完全一致,但性能差很多,目前幾乎用不到了,所以直接看另外三種類型:
-
direct
消息中的路由鍵(routing key)如果和 Binding 中的 binding key 一致, 交換器就將消息發到對應的隊列中。路由鍵與隊列名完全匹配,如果一個隊列綁定到交換機要求路由鍵爲“dog”,則只轉發 routing key 標記爲“dog”的消息,不會轉發“dog.puppy”,也不會轉發“dog.guard”等等。它是完全匹配、單播的模式。
2.fanout
每個發到 fanout 類型交換器的消息都會分到所有綁定的隊列上去。fanout 交換器不處理路由鍵,只是簡單的將隊列綁定到交換器上,每個發送到交換器的消息都會被轉發到與該交換器綁定的所有隊列上。很像子網廣播,每臺子網內的主機都獲得了一份複製的消息。fanout 類型轉發消息是最快的。
3.topic
topic 交換器通過模式匹配分配消息的路由鍵屬性,將路由鍵和某個模式進行匹配,此時隊列需要綁定到一個模式上。它將路由鍵和綁定鍵的字符串切分成單詞,這些單詞之間用點隔開。它同樣也會識別兩個通配符:符號“#”和符號“”。#匹配0個或多個單詞,匹配不多不少一個單詞。
1.安裝RabbitMQ(在此說的安裝是在Windows下安裝)
安裝RabbitMQ之前首先要安裝Erlang語言開發包,下載地址:http://www.erlang.org/download/otp_win32_R15B.exe 默認安裝即可
配置環境變量 ERLANG_HOME C:\Program Files (x86)\erl5.9
添加到PATH %ERLANG_HOME%\bin;
下載安裝RabbitMQ,下載地址:http://www.rabbitmq.com/releases/rabbitmq-server/v3.3.4/rabbitmq-server-3.3.4.exe
配置環境變量 C:\Program Files (x86)\RabbitMQ Server\rabbitmq_server-2.8.0
添加到PATH %RABBITMQ_SERVER%\sbin;
然後到dos裏面切換到RabbitMQ目錄下,執行rabbitmq-plugins.bat enable rabbitmq_management, 安裝完成之後以管理員身份啓動 rabbitmq:輸入命令:
rabbitmq-service.bat stop
rabbitmq-service.bat install
rabbitmq-service.bat start
然後,瀏覽器中輸入:127.0.0.1:15672,用戶名密碼是guest ,如果能登陸就說明安裝成功。
2.接下來要安裝php的amqp擴展
先用phpinfo()查看php版本信息,及,信息
最後根據上面的信息去下載相應的amqp版本:http://pecl.php.net/package/amqp
據上面信息我們的是32位非線程安全版本
加壓後:
將php_amqp.dll複製到php/ext,同時在php.ini中添加如下代碼:
[amqp]
extension=php_amqp.dll
然後將rabbitmq.1.dll複製到php根目錄C:/xampp/php/,同時修改apache配置文件httpd.conf,添加如下代碼:
# rabbitmq
LoadFile "C:/xampp/php/rabbitmq.1.dll"
最後重啓看看是否已經加載了amqp模塊:
---------------------------------到這裏爲止,安裝已經結束-------------------------------------------------
RabbitMQ+PHP展示實例
新建rabbit_consumer.php作爲消費者
<?php
//配置信息
$conn_args = array(
'host' => '127.0.0.1',
'port' => '5672',
'login' => 'guest',
'password' => 'guest',
'vhost'=>'/'
);
$e_name = 'e_linvo'; //交換機名
$q_name = 'q_linvo'; //隊列名
$k_route = 'key_1'; //路由key
//創建連接和channel
$conn = new AMQPConnection($conn_args);
if (!$conn->connect()) {
die("Cannot connect to the broker!\n");
}
$channel = new AMQPChannel($conn);
//創建交換機
$ex = new AMQPExchange($channel);
$ex->setName($e_name);
$ex->setType(AMQP_EX_TYPE_DIRECT); //direct類型
$ex->setFlags(AMQP_DURABLE); //持久化
echo "Exchange Status:".$ex->declare()."\n";
//創建隊列
$q = new AMQPQueue($channel);
$q->setName($q_name);
$q->setFlags(AMQP_DURABLE); //持久化
echo "Message Total:".$q->declare()."\n";
//綁定交換機與隊列,並指定路由鍵
echo 'Queue Bind: '.$q->bind($e_name, $k_route)."\n";
//阻塞模式接收消息
echo "Message:\n";
while(True){
$q->consume('processMessage');
//$q->consume('processMessage', AMQP_AUTOACK); //自動ACK應答
}
$conn->disconnect();
/**
* 消費回調函數
* 處理消息
*/
function processMessage($envelope, $queue) {
$msg = $envelope->getBody();
echo $msg."\n"; //處理消息
$queue->ack($envelope->getDeliveryTag()); //手動發送ACK應答
}
?>
新建rabbit_publisher.php作爲生產者
<?php
//配置信息
$conn_args = array(
'host' => '127.0.0.1',
'port' => '5672',
'login' => 'guest',
'password' => 'guest',
'vhost'=>'/'
);
$e_name = 'e_linvo'; //交換機名
//$q_name = 'q_linvo'; //無需隊列名
$k_route = 'key_1'; //路由key
//創建連接和channel
$conn = new AMQPConnection($conn_args);
if (!$conn->connect()) {
die("Cannot connect to the broker!\n");
}
$channel = new AMQPChannel($conn);
//創建交換機對象
$ex = new AMQPExchange($channel);
$ex->setName($e_name);
date_default_timezone_set("Asia/Shanghai");
//發送消息
//$channel->startTransaction(); //開始事務
for($i=0; $i<5; ++$i){
sleep(1);//休眠1秒
//消息內容
$message = "TEST MESSAGE!".date("h:i:sa");
echo "Send Message:".$ex->publish($message, $k_route)."\n";
}
//$channel->commitTransaction(); //提交事務
$conn->disconnect();
?>
測試一下:
先起一個窗口同樣切換到php目錄,輸入:php c:/xampp/htdocs/RabbitMQ/rabbit_consumer.php
運行消費者
然後再起一個dos窗口,切換到php根目錄,輸入以下命令:php c:/xampp/htdocs/RabbitMQ/rabbit_publisher.php
運行生產者
消費者接收到消息
這樣就模擬了隊列對消息的處理,希望我們通過這篇文章對RabbitMQ的認識都能有一定的提升。
另外給出官網的php使用指南:http://www.rabbitmq.com/tutorials/tutorial-one-php.html
相關原文鏈接:
https://www.cnblogs.com/miketwais/p/RabbitMQ.html
https://www.jianshu.com/p/79ca08116d57
https://blog.csdn.net/hellozpc/article/details/81436980