抽一根菸的時間學會.NET Core 操作RabbitMQ

青城同學發表於2021-03-03

什麼是RabbitMQ?

RabbitMQ是由erlang語言開發的一個基於AMQP(Advanced Message Queuing Protocol)協議的企業級訊息佇列中介軟體。可實現佇列,訂閱/釋出,路由,萬用字元等工作模式。

為什麼要使用RabbitMQ?

  • 非同步處理:比如傳送郵件,傳送簡訊等不需要等待處理結果的操作
  • 應用解耦:比如下單成功後,通知倉庫發貨,不需要等待倉庫回應,通過訊息佇列去通知倉庫,降低應用間耦合程式,可並行開發兩個功能模組
  • 流量削鋒:在搶購或者其他的活動頁,服務處於爆發式請求狀態,如果直連資料庫,資料庫容易被拖垮。搶購商品也容易出現庫存超賣的情況。通過佇列可有效解決該問題。
  • 日誌處理:在單機中,日誌直接寫入到檔案目錄中,但是在分散式應用中,日誌需要有統一的處理機制,可通過訊息佇列統一由某個消費端做處理。
  • 訊息通訊:如生產端和消費端可通過佇列進行非同步通訊

如何安裝RabbitMQ?

Windows端

  1. 安裝erlang語言執行環境
    https://erlang.org/download/otp_win64_23.2.exe
    下載後直接下一步即可

  2. 安裝RabbitMQ
    https://www.rabbitmq.com/install-windows.html#installer
    直接點選安裝下一步即可按章

  3. 安裝RabbitMQ的Web管理平臺

RabbitMQ的管理平臺是通過外掛的形式使用,需要手動啟用管理平臺
在Windows下,RabbitMQ預設被安裝到C:\Program Files\RabbitMQ Server\rabbitmq_server-3.8.14 下。
開啟sbin ,在cmd或者powershell中執行
rabbitmq-plugins.bat enable rabbitmq_management

安裝完成後,瀏覽器開啟 http://localhost:15672/#/ 即可看到RabbitMQ的管理介面。輸入預設賬號密碼 guest 成功登入。

Linux環境安裝

  1. Ubuntu:https://www.rabbitmq.com/install-debian.html
  2. Centos:https://www.rabbitmq.com/install-rpm.html

RabbitMQ的基本概念瞭解一下?

生產者

傳送訊息的端

消費者

獲取訊息並處理的端

Connection

一個終端連線。每一個Connection都可以在RabbitMQ後臺看到

Channel

Channel是建立在Connection上的一個虛擬通訊管道。一般情況下,往訊息佇列中寫入多條訊息,為了不每條訊息都建立一個TCP連線,所以RabbitMQ的做法是多條訊息可以公用一個Connection,大大提高MQ的負載能力。

Exchange

Exchange是一個虛擬交換機。每一條訊息都必須要通過交換機才能能進入對應的佇列,可以理解為網路裝置中的交換機,是一個意思。

Queue

Queue是一個儲存訊息的內部物件,所有的Rabbit MQ訊息都儲存在Queue中。生產者所生產的訊息會儲存在Queue中,消費者獲取的訊息也是從Queue中獲取。

如何在.NET Core中使用RabbitMQ?

nuget安裝

dotnet add package RabbitMQ.Client

建立生產者

const string QUEUENAME = "HELLO_MQ";
            //建立連線物件工廠
            var factory = new ConnectionFactory()
            {
                UserName = "guest",
                Password = "guest",
                HostName = "localhost",
                Port = 5672,  //RabbitMQ預設的埠
            };

            while (true)
            {
                using var conn = factory.CreateConnection();
                var chanel = conn.CreateModel();

                chanel.QueueDeclare(QUEUENAME, true, false, false);
                Console.WriteLine("輸入生產內容:");
                var input = Console.ReadLine();
                chanel.BasicPublish("", QUEUENAME, null, Encoding.Default.GetBytes("hello rabbitmq:" + input));
            }

在迴圈中,輸入一個值,按下enter,即可推送一條訊息到佇列。

也可以直接在RabbitMQ的管理後臺檢視

可以看到我們傳送的訊息已經被RabbitMQ儲存在Queue中了。只等某個幸運的消費者前來消費。

建立消費者

const string QUEUENAME = "HELLO_MQ";
            var factory = new ConnectionFactory()
            {
                UserName = "guest",
                Password = "guest",
                HostName = "localhost",
                Port = 5672,
            };

            var conn = factory.CreateConnection();
            var chanel = conn.CreateModel();
            chanel.QueueDeclare(QUEUENAME, true, false, false);
            EventingBasicConsumer consumer = new EventingBasicConsumer(chanel);
            consumer.Received += (a, e) =>
            {
                Console.WriteLine($"{DateTime.Now.ToString()}接收到訊息:" + Encoding.Default.GetString(e.Body.ToArray()));
                chanel.BasicAck(e.DeliveryTag, true); //收到回覆後,RabbitMQ會直接在佇列中刪除這條訊息
            };
            chanel.BasicConsume(QUEUENAME, false, consumer);

            Console.WriteLine("啟動成功");
            Console.ReadLine();

啟動成功後,consumer的Received方法,會收到一條來自MQ的訊息,

如果處理完成後,不呼叫chennel的BasicAck方法,那麼這條訊息依然會存在,下次有消費者出現,會再次推送給消費者。

簡單的RabbitMQ Hello World到這裡就算完成了。接下來就是稍微高階一點的應用

RabbitMQ的工作模式

Work Queue 工作佇列模式

工作佇列模式的意思就是一個生產者對應多個消費者。RabbitMQ會使用輪詢去給每個消費者傳送訊息。

publish/subscribe

釋出訂閱模式是屬於比較用多的一種。

釋出訂閱,是由交換機發布訊息給多個佇列。多個佇列再對應多個消費者。

釋出訂閱模式對應的交換機型別的fanout。

消費者

A

const string QUEUENAME = "HELLO_MQ_B";
            const string TESTEXCHANGE = "TESTEXCHANGE";
            var factory = new ConnectionFactory()
            {
                UserName = "guest",
                Password = "guest",
                HostName = "localhost",
                Port = 5672,
            };

            var conn = factory.CreateConnection();
            var channel = conn.CreateModel();
            //定義佇列
            channel.QueueDeclare(QUEUENAME, true, false, false);
            //定義交換機
            channel.ExchangeDeclare(TESTEXCHANGE, ExchangeType.Fanout, true, false);
            //繫結佇列到交換機
            channel.QueueBind(QUEUENAME, TESTEXCHANGE, "");
            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += (a, e) =>
            {
                Console.WriteLine($"{DateTime.Now.ToString()}接收到訊息:" + Encoding.Default.GetString(e.Body.ToArray()));
                channel.BasicAck(e.DeliveryTag, true); //收到回覆後,RabbitMQ會直接在佇列中刪除這條訊息
            };
            channel.BasicConsume(QUEUENAME, false, consumer);

            Console.WriteLine("啟動成功");
            Console.ReadLine();

B

const string QUEUENAME = "HELLO_MQ";
            const string TESTEXCHANGE = "TESTEXCHANGE";
            var factory = new ConnectionFactory()
            {
                UserName = "guest",
                Password = "guest",
                HostName = "localhost",
                Port = 5672,
            };

            var conn = factory.CreateConnection();
            var channel = conn.CreateModel();
            //定義佇列
            channel.QueueDeclare(QUEUENAME, true, false, false);
            //定義交換機
            channel.ExchangeDeclare(TESTEXCHANGE, ExchangeType.Fanout, true, false);
            //繫結佇列到交換機
            channel.QueueBind(QUEUENAME, TESTEXCHANGE, "");
            var consumer = new EventingBasicConsumer(channel);
            consumer.Received += (a, e) =>
            {
                Console.WriteLine($"{DateTime.Now.ToString()}接收到訊息:" + Encoding.Default.GetString(e.Body.ToArray()));
                channel.BasicAck(e.DeliveryTag, true); //收到回覆後,RabbitMQ會直接在佇列中刪除這條訊息
            };
            channel.BasicConsume(QUEUENAME, false, consumer);

            Console.WriteLine("啟動成功");
            Console.ReadLine();

生產者

const string QUEUENAME = "HELLO_MQ";
            const string QUEUENAME_B = "HELLO_MQ_B";
            const string TESTEXCHANGE = "TESTEXCHANGE";

            //建立連線物件工廠
            var factory = new ConnectionFactory()
            {
                UserName = "guest",
                Password = "guest",
                HostName = "localhost",
                Port = 5672,  //RabbitMQ預設的埠
            };
            using var conn = factory.CreateConnection();
            while (true)
            {

                var channel = conn.CreateModel();
                //定義交換機
                channel.ExchangeDeclare(TESTEXCHANGE, ExchangeType.Fanout, true, false);
                Console.WriteLine("輸入生產內容:");
                var input = Console.ReadLine();
                channel.BasicPublish(TESTEXCHANGE,"", null, Encoding.Default.GetBytes("hello rabbitmq:" + input));
            }

在生產者執行成功後,RabbitMQ後臺會出現一個交換機,點選交換機會看到交換機下繫結了兩個佇列


從生產者傳送訊息到佇列,兩個消費者會同時收到訊息

routing模式

routing模式對應的交換機型別是direct,和釋出訂閱模式的區別在於:routing模式下,可以指定一個routingkey,用於區分訊息
生產者

    var channel = conn.CreateModel();
                //定義交換機
                channel.ExchangeDeclare(TESTEXCHANGE, ExchangeType.Direct, true, false);
                //繫結佇列到交換機
                Console.WriteLine("輸入生產內容:");
                var input = Console.ReadLine();
                channel.BasicPublish(TESTEXCHANGE, "INFO", null, Encoding.Default.GetBytes("hello rabbitmq:" + input));

消費者 A

  //定義佇列
            channel.QueueDeclare(QUEUENAME, true, false, false);
            //定義交換機
            channel.ExchangeDeclare(TESTEXCHANGE, ExchangeType.Direct, true, false);
            //繫結佇列到交換機
            channel.QueueBind(QUEUENAME, TESTEXCHANGE, "INFO");

消費者 B

  //定義佇列
            channel.QueueDeclare(QUEUENAME, true, false, false);
            //定義交換機
            channel.ExchangeDeclare(TESTEXCHANGE, ExchangeType.Direct, true, false);
            //繫結佇列到交換機
            channel.QueueBind(QUEUENAME, TESTEXCHANGE, "ERROR");

繫結成功後,傳送訊息,消費者A可以收到訊息,消費者B無法收到訊息。

如果遇到指定routingKey生產一條訊息,結果 AB消費者都收到的情況。建議在RabbitMQ後臺的交換機下看一下繫結的Queue是否重複繫結了多個routingKey.

topic萬用字元模式

在萬用字元模式下,RabbitMQ使用模糊匹配來決定把訊息推送給哪個生產者。萬用字元有兩個符號來匹配routingKey

  1. *匹配一個字元 如:*.qq.com 可匹配 1.qq.com
  2. #匹配一個或者多個字元。 如:*.qq.com 可匹配 1.qq.com或者1111.qq.com

其他的操作基本和routing模式一樣。

header模式

header模式是把routingkey放到header中.取消掉了routingKey。並使用一個字典傳遞 K、V的方式來匹配。
比如同時要給使用者傳送郵件和簡訊,可直接通過header的鍵值對來匹配繫結的值,把訊息傳遞給發簡訊和郵件的生產者.


廣告時間:

成都南門這邊有招BS方向高階.NET程式設計師的公司嗎? 有的話,請私聊我。或者加我QQ:862640563


部落格地址:https://www.cnblogs.com/boxrice/ 轉載請註明出處

相關文章