一、RabbitMQ是什麼?
AMQP,即Advanced Message Queuing Protocol,高階訊息佇列協議,是應用層協議的一個開放標準,為面向訊息的中介軟體設計。訊息中介軟體主要用於元件之間的解耦,訊息的傳送者無需知道訊息使用者的存在,反之亦然。
AMQP的主要特徵是面向訊息、佇列、路由(包括點對點和釋出/訂閱)、可靠性、安全。
RabbitMQ是一個開源的AMQP實現,伺服器端用Erlang語言編寫,支援多種客戶端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支援AJAX。用於在分散式系統中儲存轉發訊息,在易用性、擴充套件性、高可用性等方面表現不俗。
二、訊息佇列的特性
解耦:訊息的生產者與消費者均基於AMQP協議(相同的介面與規範)進行傳送與接收訊息,互相不存依賴;
冗餘:訊息只有處理了才會被刪除,除非明確允許多個消費者可以收到同一訊息的多個副本,否則每個訊息只會被單個消費者接收並處理;
擴充套件性:可增加或減少多個訊息的生產者與消費者,兩者的改動均不會影響到雙方;
靈活性 & 峰值處理能力:因為有良好的擴充套件性,所以可視伺服器的處理情況【可稱為:消費者】(比如:高併發負載過大)動態的增減伺服器,以提提高處理能力(可稱為:負載均衡);
可恢復性:訊息的生產者與消費者不論哪一方出現問題,均不會影響訊息的正常發出與接收(當然單一的生產者與消費者除外,如果是這樣也就沒有必要使用分散式訊息佇列);
送達保證:只有訊息被確認成功處理後才會被刪除,否則會重新分發給其它的消費者進行處理,直到確認處理成功為止;
排序保證:先進先出是佇列的基本特性;
緩衝:同一時間有多個訊息進入訊息佇列,但是同一時間可以指定一個多個訊息被訊息者接收並處理,其餘的訊息處理等待狀態,這樣可以降低伺服器的壓力,起到緩衝的作用;
理解資料流:傳遞的訊息內容以位元組陣列為主,但可以將物件序列化後成位元組陣列,然後在消費者接收到訊息後,可反序列化成物件並進行相關的處理,應用場景:CQRS;
非同步通訊:允許將一個或多個訊息放入訊息佇列,但並不立即處理它,而是在恰當的時候再去由一個或多個消費者分別接收並處理它們;
以上是我的個人理解,也可參看《使用訊息佇列的 10 個理由》
應用場景:針對高併發且無需立即返回處理結果的時候,可以考慮使用訊息佇列,如果處理需要立即返回結果則不適合;
三、RabbitMQ環境的安裝
1.伺服器端:
A.需要先安裝Erlang環境,下載地址:http://www.erlang.org/download.html,可能有時無法正常訪問,可以通過VPN代理來訪問該網站或在其它網站上下載(比如:CSDN)
B.安裝RabbitMQ Server(有針對多個作業系統的下載,我這邊以WINDOWS平臺為主),下載地址:http://www.rabbitmq.com/download.html,
說明:最新版的Erlang及abbitMQ Server安裝後,一般WINDOWS環境變數及服務均都已正常安裝與並正常啟動,可不是最新版或沒有安裝好,則可執行以下命令:
Setx ERLANG_HOME “C:\Program Files\erl7.1″ -Erlang的-安裝目錄,也可通過系統屬性-->高階-->環境變數來手動設定;
cd C:\Program Files (x86)\RabbitMQ Server\rabbitmq_server-3.5.6\sbin --切換到RabbitMQ Server的sbin目錄下,然後執行如下命令:
rabbitmq-service install
rabbitmq-service enable
rabbitmq-service start
安裝並設定OK後,可以通過:rabbitmqctl status檢視執行情況、rabbitmqctl list_users檢視當前使用者、以下命令增加一個新使用者:
rabbitmqctl add_user username password
rabbitmqctl set_permissions username ".*" ".*" ".*"
rabbitmqctl set_user_tags username administrator
修改密碼:rabbitmqctl change_password username newpassowrd
刪除指定的使用者:rabbitmqctl delete_user username
列出所有queue:rabbitmqctl list_queues
列出指定queue的資訊:rabbitmqctl list_queues [the queue name] messages_ready messages_unacknowledged
列出所有exchange:rabbitmqctl list_exchanges
列出所有binding:rabbitmqctl list_bindings
安裝基於web的管理外掛:rabbitmq-plugins.bat enable rabbitmq_management
當然還有其它的命令,大家可以去檢視官網及其它資料,但我認為知道以上的命令足夠用了
四、RabbitMQ的基本用法
使用RabbitMQ客戶端就必需在專案中引用其相關的元件,這裡可以通過NuGet安裝或從官網下載再引用均可,方法很簡單,不再重述;
1.普通用法:採用預設的exchange(交換機,或稱路由器)+預設的exchange型別:direct+noAck(自動應答,接收就應答)
/// <summary> /// 訊息傳送者,一般用在客戶端 /// </summary> class RabbitMQPublish { static void Main(string[] args) { var factory = new ConnectionFactory();//建立連線工廠並初始連線 factory.HostName = "localhost"; factory.UserName = "zwj"; factory.Password = "www.zuowenjun.cn"; using (var connection = factory.CreateConnection())//建立一個連線 { using (var channel = connection.CreateModel()) //建立一個通道 { channel.QueueDeclare("hello", false, false, false, null);//建立一個佇列 string message = ""; while (message!="exit") { Console.Write("Please enter the message to be sent:"); message = Console.ReadLine(); var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish("", "hello", null, body); //傳送訊息 Console.WriteLine("set message: {0}", message); } } } } } /// <summary> /// 消費者,一般用在服務端 /// </summary> class RabbitMQConsume { static void Main(string[] args) { var factory = new ConnectionFactory();//建立連線工廠並初始連線 factory.HostName = "localhost"; factory.UserName = "zwj"; factory.Password = "www.zuowenjun.cn"; using (var connection = factory.CreateConnection())//建立一個連線 { using (var channel = connection.CreateModel())//建立一個通道 { channel.QueueDeclare("hello", false, false, false, null);//建立一個佇列 var consumer = new QueueingBasicConsumer(channel);//建立一個消費者 channel.BasicConsume("hello", true, consumer);//開啟訊息者與通道、佇列關聯 Console.WriteLine(" waiting for message."); while (true) { var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();//接收訊息並出列 var body = ea.Body;//訊息主體 var message = Encoding.UTF8.GetString(body); Console.WriteLine("Received {0}", message); if (message == "exit") { Console.WriteLine("exit!"); break; } } } } } }
2.負載均衡處理模式:採用預設的exchange(交換機)+智慧分發+預設的exchange型別:direct+手動應答
訊息生產者/釋出者程式碼與上面相同;
以下是消費者程式碼:
/// <summary> /// 消費者,一般用在服務端 /// </summary> class RabbitMQConsume { static void Main(string[] args) { var factory = new ConnectionFactory();//建立連線工廠並初始連線 factory.HostName = "localhost"; factory.UserName = "zwj"; factory.Password = "www.zuowenjun.cn"; using (var connection = factory.CreateConnection())//建立一個連線 { using (var channel = connection.CreateModel())//建立一個通道 { channel.QueueDeclare("hello", false, false, false, null);//建立一個佇列 channel.BasicQos(0, 1, false);//在一個工作者還在處理訊息,並且沒有響應訊息之前,不要給他分發新的訊息。相反,將這條新的訊息傳送給下一個不那麼忙碌的工作者。 var consumer = new QueueingBasicConsumer(channel);//建立一個消費者 channel.BasicConsume("hello", false, consumer);//開啟訊息者與通道、佇列關聯 Console.WriteLine(" waiting for message."); while (true) { var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();//接收訊息並出列 var body = ea.Body;//訊息主體 var message = Encoding.UTF8.GetString(body); Console.WriteLine("Received {0}", message); channel.BasicAck(ea.DeliveryTag, false); if (message == "exit") { Console.WriteLine("exit!"); break; } Thread.Sleep(1000); } } } } }
3.訊息持久化模式:在2的基礎上加上持久化,這樣即使生產者或消費者或服務端斷開,訊息均不會丟失
/// <summary> /// 訊息傳送者,一般用在客戶端 /// </summary> class RabbitMQPublish { static void Main(string[] args) { var factory = new ConnectionFactory();//建立連線工廠並初始連線 factory.HostName = "localhost"; factory.UserName = "zwj"; factory.Password = "www.zuowenjun.cn"; using (var connection = factory.CreateConnection())//建立一個連線 { using (var channel = connection.CreateModel()) //建立一個通道 { channel.QueueDeclare("hello", true, false, false, null);//建立一個佇列,第2個引數為true表示為持久佇列 var properties = channel.CreateBasicProperties(); //properties.SetPersistent(true);這個方法提示過時,不建議使用 properties.DeliveryMode = 2;//1表示不持久,2.表示持久化 string message = ""; while (message!="exit") { Console.Write("Please enter the message to be sent:"); message = Console.ReadLine(); var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish("", "hello", properties, body); //傳送訊息 Console.WriteLine("set message: {0}", message); } } } } } /// <summary> /// 消費者,一般用在服務端 /// </summary> class RabbitMQConsume { static void Main(string[] args) { var factory = new ConnectionFactory();//建立連線工廠並初始連線 factory.HostName = "localhost"; factory.UserName = "zwj"; factory.Password = "www.zuowenjun.cn"; using (var connection = factory.CreateConnection())//建立一個連線 { using (var channel = connection.CreateModel())//建立一個通道 { channel.QueueDeclare("hello", true, false, false, null);//建立一個佇列,第2個引數為true表示為持久佇列 channel.BasicQos(0, 1, false);//在一個工作者還在處理訊息,並且沒有響應訊息之前,不要給他分發新的訊息。相反,將這條新的訊息傳送給下一個不那麼忙碌的工作者。 var consumer = new QueueingBasicConsumer(channel);//建立一個消費者 channel.BasicConsume("hello", false, consumer);//開啟訊息者與通道、佇列關聯 Console.WriteLine(" waiting for message."); while (true) { var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();//接收訊息並出列 var body = ea.Body;//訊息主體 var message = Encoding.UTF8.GetString(body); Console.WriteLine("Received {0}", message); channel.BasicAck(ea.DeliveryTag, false); if (message == "exit") { Console.WriteLine("exit!"); break; } Thread.Sleep(1000); } } } } }
4.廣播訂閱模式:定義一個交換機,其型別設為廣播型別,傳送訊息時指定這個交換機,消費者的訊息佇列繫結到該交換機實現訊息的訂閱,訂閱後則可接收訊息,未訂閱則無法收到訊息
/// <summary> /// 訊息傳送者/生產者,一般用在客戶端 /// </summary> class RabbitMQPublish { static void Main(string[] args) { var factory = new ConnectionFactory();//建立連線工廠並初始連線 factory.HostName = "localhost"; factory.UserName = "zwj"; factory.Password = "www.zuowenjun.cn"; using (var connection = factory.CreateConnection())//建立一個連線 { using (var channel = connection.CreateModel()) //建立一個通道 { channel.ExchangeDeclare("publish", "fanout",true);//定義一個交換機,且採用廣播型別,並設為持久化 string queueName = channel.QueueDeclare("hello", true, false, false, null);//建立一個佇列,第2個引數為true表示為持久佇列,這裡將結果隱式轉換成string var properties = channel.CreateBasicProperties(); //properties.SetPersistent(true);這個方法提示過時,不建議使用 properties.DeliveryMode = 2;//1表示不持久,2.表示持久化 string message = ""; while (message!="exit") { Console.Write("Please enter the message to be sent:"); message = Console.ReadLine(); var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish("publish", "hello", properties, body); //傳送訊息,這裡指定了交換機名稱,且routeKey會被忽略 Console.WriteLine("set message: {0}", message); } } } } } /// <summary> /// 消費者,一般用在服務端 /// </summary> class RabbitMQConsume { static void Main(string[] args) { var factory = new ConnectionFactory();//建立連線工廠並初始連線 factory.HostName = "localhost"; factory.UserName = "zwj"; factory.Password = "www.zuowenjun.cn"; using (var connection = factory.CreateConnection())//建立一個連線 { using (var channel = connection.CreateModel())//建立一個通道 { channel.ExchangeDeclare("publish", "fanout", true);//定義一個交換機,且採用廣播型別,並持久化該交換機,並設為持久化 string queueName = channel.QueueDeclare("hello", true, false, false, null);//建立一個佇列,第2個引數為true表示為持久佇列 channel.QueueBind(queueName, "publish", "");//將佇列繫結到名publish的交換機上,實現訊息訂閱 channel.BasicQos(0, 1, false);//在一個工作者還在處理訊息,並且沒有響應訊息之前,不要給他分發新的訊息。相反,將這條新的訊息傳送給下一個不那麼忙碌的工作者。 var consumer = new QueueingBasicConsumer(channel);//建立一個消費者 channel.BasicConsume(queueName, false, consumer);//開啟訊息者與通道、佇列關聯 Console.WriteLine(" waiting for message."); while (true) { var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();//接收訊息並出列 var body = ea.Body;//訊息主體 var message = Encoding.UTF8.GetString(body); Console.WriteLine("Received {0}", message); channel.BasicAck(ea.DeliveryTag, false);//應答 if (message == "exit") { Console.WriteLine("exit!"); break; } Thread.Sleep(1000); } } } } }
5.主題訂閱模式:定義一個交換機,其型別設為主題訂閱型別,傳送訊息時指定這個交換機及RoutingKey,消費者的訊息佇列繫結到該交換機並匹配到RoutingKey實現訊息的訂閱,訂閱後則可接收訊息,未訂閱則無法收到訊息
/// <summary> /// 訊息傳送者/生產者,一般用在客戶端 /// </summary> class RabbitMQPublish { static void Main(string[] args) { var factory = new ConnectionFactory();//建立連線工廠並初始連線 factory.HostName = "localhost"; factory.UserName = "zwj"; factory.Password = "www.zuowenjun.cn"; using (var connection = factory.CreateConnection())//建立一個連線 { using (var channel = connection.CreateModel()) //建立一個通道 { channel.ExchangeDeclare("publish-topic", "topic", true);//定義一個交換機,且採用廣播型別,並持久化該交換機 channel.QueueDeclare("hello-mq", true, false, false, null);//建立一個佇列,第2個引數為true表示為持久佇列 var properties = channel.CreateBasicProperties(); //properties.SetPersistent(true);這個方法提示過時,不建議使用 properties.DeliveryMode = 2;//1表示不持久,2.表示持久化 string message = ""; while (message!="exit") { Console.Write("Please enter the message to be sent:"); message = Console.ReadLine(); var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish("publish-topic", "hello.test", properties, body); //傳送訊息,這裡指定了交換機名稱,且routeKey會被忽略 Console.WriteLine("set message: {0}", message); } } } } } /// <summary> /// 消費者,一般用在服務端 /// </summary> class RabbitMQConsume { static void Main(string[] args) { var factory = new ConnectionFactory();//建立連線工廠並初始連線 factory.HostName = "localhost"; factory.UserName = "zwj"; factory.Password = "www.zuowenjun.cn"; using (var connection = factory.CreateConnection())//建立一個連線 { using (var channel = connection.CreateModel())//建立一個通道 { channel.ExchangeDeclare("publish-topic", "topic",true);//定義一個交換機,且採用廣播型別,並持久化該交換機 string queueName = channel.QueueDeclare("hello-mq", true, false, false, null);//建立一個佇列,第2個引數為true表示為持久佇列 channel.QueueBind(queueName, "publish-topic", "*.test");//將佇列繫結到路由上,實現訊息訂閱 channel.BasicQos(0, 1, false);//在一個工作者還在處理訊息,並且沒有響應訊息之前,不要給他分發新的訊息。相反,將這條新的訊息傳送給下一個不那麼忙碌的工作者。 var consumer = new QueueingBasicConsumer(channel);//建立一個消費者 channel.BasicConsume(queueName, false, consumer);//開啟訊息者與通道、佇列關聯 Console.WriteLine(" waiting for message."); while (true) { var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();//接收訊息並出列 var body = ea.Body;//訊息主體 var message = Encoding.UTF8.GetString(body); Console.WriteLine("Received {0}", message); channel.BasicAck(ea.DeliveryTag, false);//應答 if (message == "exit") { Console.WriteLine("exit!"); break; } Thread.Sleep(1000); } } } } }
交換機路由型別如下:
Direct Exchange:直接匹配,通過Exchange名稱+RoutingKey來傳送與接收訊息;
Fanout Exchange:廣播訂閱,向所有消費者釋出訊息,但只有消費者將佇列繫結到該路由才能收到訊息,忽略RoutingKey;
Topic Exchange:主題匹配訂閱,這裡的主題指的是RoutingKey,RoutingKey可以採用萬用字元,如:*或#,RoutingKey命名採用.來分隔多個詞,只有消費者將佇列繫結到該路由且指定的RoutingKey符合匹配規則時才能收到訊息;
Headers Exchange:訊息頭訂閱,訊息釋出前,為訊息定義一個或多個鍵值對的訊息頭,然後消費者接收訊息時同樣需要定義類似的鍵值對請求頭,裡面需要多包含一個匹配模式(有:x-mactch=all,或者x-mactch=any),只有請求頭與訊息頭相匹配,才能接收到訊息,忽略RoutingKey;
本文內容參考了以下文章: