總結訊息佇列RabbitMQ的基本用法

夢在旅途發表於2015-11-17

一、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;

本文內容參考了以下文章:

.NET 環境中使用RabbitMQ

.Net下RabbitMQ的使用系列文章

相關文章