Net中使用 RabbitMq | Return 消息不可達處理機制

生產者通過指定一個 exchange 和 routingkey 把消息送達到某個隊列中去,然後消費者監聽隊列,進行消費處理。但是在某些情況下,如果我們在發送消息時,當前的 exchange 不存在或者指定的 routingkey 路由不到,這個時候如果要監聽這種不可達的消息,就要使用 return

項目建議先啓動消費端,再啓動生產端(因爲我的交換機和隊列都是在消費端創建的,交換機與隊列綁定指定的路由也是在消費者端指定的,生產端並沒有創建交換機,生產者想發送一條消息到MQ服務器,就必須要有交換機,因爲只有交換機才能接收到消息,然後交換機把消息保存到Queue隊列中,消息保存到隊列的前提是隊列與交換機進行了綁定,這個綁定的過程可能指定了RoutingKey路由,也可能沒有指定路由(比如Fanout模式下就不需要指定路由))

假設你事先已經知道MQ服務器中已經有vhost001這個虛擬地址,  vhost001這個虛擬地址下 有一個ex.user的交換機,與他綁定的Queue隊列中有一個名字爲queue.user的隊列,這個交換機與隊列的路由是routing.user ,那麼你先啓動生產者也可以。因爲這些指定名稱的交換機,隊列,路由都已經事先在RabbitMq服務器上存在了。

 

ProducterApp:生產者端

假設MQ服務器中已經有vhost001這個虛擬地址

假設vhost001這個虛擬地址下 有一個ex.user的交換機

假設ex.user這個交換機與名字爲queue.user的這個隊列進行綁定了,他們綁定並指定的路由爲 routing.user

那麼下面這個生產者向MQ服務器發送消息是不可達的(因爲我發消息的時候已經指定了這條消息往ex.user交換機中發,這個交換機使用routing.abc這個RoutingKey去找與這個ex.user交換機綁定的隊列,而MQ服務器中ex.user交換機發現與之綁定的隊列中沒有routing.abc這條路由,所以消息不可達),這條不可達的消息將會被 EventHandler<BasicReturnEventArgs>事件所監聽到。(前提是在外面發送消息的時候,將mandatory:設爲true了,否則這條不可達的消息將被刪除)

假設ex.user這個交換機與名字爲queue.abc的這個隊列進行綁定了,他們綁定並指定的路由爲 routing.abc

那麼下面這個生產者向MQ服務器發送消息是可達的。

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;

namespace ProducterApp
{
    class Program
    {
        /// 連接配置
        /// </summary>
        private static readonly ConnectionFactory rabbitMqFactory = new ConnectionFactory() //創建一個工廠連接對象
        {
            HostName = "192.168.31.30",
            UserName = "admin",
            Password = "admin",
            VirtualHost = "/vhost001",
            Port = 5672,  
            AutomaticRecoveryEnabled = true,//網絡故障自動連接恢復
        };
        /// <summary>
        /// 路由名稱
        /// </summary>
        const string ExchangeName = "ex.user";
        const string routingKey = "routing.abc";
        //隊列名稱
        //const string QueueName = "queue010";
        static void Main(string[] args)
        {
            using (IConnection conn = rabbitMqFactory.CreateConnection()) //創建一個連接
            {
                using (IModel channel = conn.CreateModel()) //創建一個Channel
                {                   

                    IBasicProperties props = channel.CreateBasicProperties();
                    props.DeliveryMode = 2; //1:非持久化 2:持續久化 (即:當值爲2的時候,我們一個消息發送到服務器上之後,如果消息還沒有被消費者消費,服務器重啓了之後,這條消息依然存在)
                    props.Persistent = true;
                    props.ContentEncoding = "UTF-8"; //注意要大寫

                    //我們也可以設定自定義屬性,把自定義的屬性放到IBasicProperties的Headers中進行發送
                    var dir = new Dictionary<string, object>();
                    dir.Add("Name", "lily");//特別要注意,字符串傳遞過去的時候是實際是以byte[]數組的形式傳遞過去的
                    dir.Add("Age", 25);
                    props.Headers = dir;



                    //這個事件就是用來監聽我們一些不可達的消息的內容的:比如某些情況下,如果我們在發送消息時,當前的exchange不存在或者指定的routingkey路由不到,這個時候如果要監聽這種不可達的消息,就要使用 return
                    EventHandler<BasicReturnEventArgs> evreturn = new EventHandler<BasicReturnEventArgs>((o, basic) => {
                        Console.WriteLine(basic.ReplyCode); //消息失敗的code
                        Console.WriteLine(basic.ReplyText); ////描述返回原因的文本
                        Console.WriteLine(Encoding.UTF8.GetString(basic.Body)); //失敗消息的內容

                        //在這裏我們可能要對這條不可達消息做處理,比如是否重發這條不可達的消息呀,或者這條消息發送到其他的路由中呀,等等
                        System.IO.File.AppendAllText("d:/return.txt", "調用了Return;ReplyCode:" + basic.ReplyCode + ";ReplyText:" + basic.ReplyText + ";Body:" + Encoding.UTF8.GetString(basic.Body));
                    });
                    channel.BasicReturn += evreturn;

                                       
                    for (int i = 0; i < 5; i++) //發5條消息到MQ服務器
                    {
                        props.MessageId = Guid.NewGuid().ToString("N"); //設定這條消息的MessageId(每條消息的MessageId都是唯一的)

                        string msg = "你好,這是我的第" + i + "條消息;時間:" + DateTime.Now.ToString("yyyy:MM:dd");
                        var msgBody = Encoding.UTF8.GetBytes(msg); //發送的消息必須是二進制的

                        //記住:如果需要EventHandler<BasicReturnEventArgs>事件監聽不可達消息的時候,一定要將mandatory設爲true
                        channel.BasicPublish(exchange: ExchangeName, routingKey: routingKey, mandatory:true, basicProperties: props, body: msgBody);
                    }
                    Console.ReadKey();
                }
            }
        }
    }
}

CustomerApp:消費者端

特別注意:生產者向MQ服務器發送消息的可達與不可達,與消費者沒有任何關係。我之所以把消費者端的代碼放上來,僅僅只是我在消費者端創建了交換機,隊列,還有交換機與隊列綁定所指定的路由,從而確保MQ服務器有這些東西

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;
using System.Threading;

namespace CustomerApp
{
    class Program
    {
        /// <summary>
        /// 連接配置
        /// </summary>
        private static readonly ConnectionFactory rabbitMqFactory = new ConnectionFactory()
        {
            HostName = "192.168.31.30",
            UserName = "admin",
            Password = "admin",
            Port = 5672,
            VirtualHost = "/vhost001",
        };
        /// <summary>
        /// 路由名稱
        /// </summary>
        const string ExchangeName = "ex.user";

        //隊列名稱
        const string QueueName = "queue.user";
        const string routingKey = "routing.user"; //這裏routingkey與消費端的routingkey不保持一致的原因就是要測試生產端的消息不可達,測試不可達所產生的事件調用
        static void Main(string[] args)
        {
            using (IConnection conn = rabbitMqFactory.CreateConnection())
            {
                using (IModel channel = conn.CreateModel())
                {
                   
					//創建交換機
                    channel.ExchangeDeclare(ExchangeName, ExchangeType.Direct, durable: true, autoDelete: false, arguments: null);
					//創建Queue隊列
                    channel.QueueDeclare(QueueName, durable: true, autoDelete: false, exclusive: false, arguments: null);
					//交換機與隊列進行綁定,並指定了他們的路由
                    channel.QueueBind(QueueName, ExchangeName, routingKey: routingKey); 
					//創建一個消費者
                    EventingBasicConsumer consumer = new EventingBasicConsumer(channel);                     
                    consumer.Received += (o, basic) =>//通過訂閱檢索消息:這個訂閱是一個EventHandler<BasicDeliverEventArgs>類型事件
                    {                        
                        var msgBody = basic.Body; //獲取消息內容
                        var a = basic.ConsumerTag;
                        var b = basic.DeliveryTag;
                        var c = basic.Redelivered;
                        var e = basic.RoutingKey;
                        var f = basic.BasicProperties.Headers;
                        Console.WriteLine(string.Format("接收時間:{0},消息內容:{1}", DateTime.Now.ToString("HH:mm:ss"), Encoding.UTF8.GetString(msgBody)));
                    };
                    channel.BasicConsume(QueueName, autoAck: true, consumer: consumer);//第二個參數autoAck設爲true爲自動應答,false爲手動ack 
                    Console.ReadKey();
                }
            }
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章