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();
                }
            }
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值