.net core 下使用 RabbitMQ 失败重试 (七)
主要 代码
消息的 Properties.headers.x-death 属性中查询到消息投递源信息和消息被投递的次数;
1 2 var consumer = new EventingBasicConsumer(channel); 3 consumer.Received += (model, ea) => 4 { 5 var _message = (BasicDeliverEventArgs)ea; //消息传送参数 6 var _headers = _message.BasicProperties.Headers; //消息头 7 List<object> xdeaths = (List<object>)_headers["x-death"]; 8 Dictionary<string, object> result = (Dictionary<string, object>)xdeaths[0]; 9 long count = (long)result["count"]; 10 11 var body = ea.Body.ToArray(); 12 var message = Encoding.UTF8.GetString(body); 13 Console.WriteLine(ea.RoutingKey); 14 Console.WriteLine(" [x] Received {0}", message); 15 };
核心方法
-
BasicAck 用于确认当前消
-
BasicNack 用于拒绝当前消息,可以执行批量拒绝
-
BasicReject 用于拒绝当前消息,仅拒绝一条消息
消息拒收 查看下面文章
(21条消息) .Net Core&RabbitMQ消息转发可靠机制(上)_寒冰屋的博客-CSDN博客
1 using RabbitMQ.Client; 2 using RabbitMQ.Client.Events; 3 using System; 4 using System.Collections.Generic; 5 using System.Linq; 6 using System.Text; 7 using System.Threading.Tasks; 8 9 namespace RabbitMQ_Consumer.Dead 10 { 11 /// <summary> 12 /// 什么是消息确认机制? 13 /// MQ消息确认类似于数据库中用到的 commit 语句,用于告诉broker本条消息是被消费成功了还是失败了; 14 /// 平时默认消息在被接收后就被自动确认了,需要在创建消费者时、设置 autoAck: false 即可使用手动确认模式; 15 /// ==================================================================================== 16 /// 什么是死信队列? 17 /// 死信队列是用于接收普通队列发生失败的消息,其原理与普通队列相同; 18 /// > 失败消息如:被消费者拒绝的消息、TTL超时的消息、队列达到最大数量无法写入的消息; 19 /// 死信队列创建方法: 20 /// > 在创建普通队列时,在参数"x-dead-letter-exchange"中定义失败消息转发的目标交换机; 21 /// > 再创建一个临时队列,订阅"x-dead-letter-exchange"中指定的交换机; 22 /// > 此时的临时队列就能接收到普通队列失败的消息了; 23 /// > 可在消息的 Properties.headers.x-death 属性中查询到消息投递源信息和消息被投递的次数; 24 /// </summary> 25 public class DeadExchange 26 { 27 private static string _exchangeNormal = "Exchange.Normal"; //定义一个用于接收 正常 消息的交换机 28 private static string _exchangeRetry = "Exchange.Retry"; //定义一个用于接收 重试 消息的交换机 29 private static string _exchangeFail = "Exchange.Fail"; //定义一个用于接收 失败 消息的交换机 30 private static string _queueNormal = "Queue.Noraml"; //定义一个用于接收 正常 消息的队列 31 private static string _queueRetry = "Queue.Retry"; //定义一个用于接收 重试 消息的队列 32 private static string _queueFail = "Queue.Fail"; //定义一个用于接收 失败 消息的队列 33 34 public static void TestDemo() 35 { 36 var connection = RabbitMQHelper.GetConnection(); 37 var channel = connection.CreateModel(); 38 39 //声明交换机 40 channel.ExchangeDeclare(exchange: _exchangeNormal, type: "topic"); 41 channel.ExchangeDeclare(exchange: _exchangeRetry, type: "topic"); 42 channel.ExchangeDeclare(exchange: _exchangeFail, type: "topic"); 43 44 //定义队列参数 45 var queueNormalArgs = new Dictionary<string, object>(); 46 { 47 // 绑定失败交互交换机 48 queueNormalArgs.Add("x-dead-letter-exchange", _exchangeFail); //指定死信交换机,用于将 Noraml 队列中失败的消息投递给 Fail 交换机 49 } 50 var queueRetryArgs = new Dictionary<string, object>(); 51 { 52 queueRetryArgs.Add("x-dead-letter-exchange", _exchangeNormal); //指定死信交换机,用于将 Retry 队列中超时的消息投递给 Noraml 交换机 53 queueRetryArgs.Add("x-message-ttl", 6000); //定义 queueRetry 的消息最大停留时间 (原理是:等消息超时后由 broker 自动投递给当前绑定的死信交换机) 54 //定义最大停留时间为防止一些 待重新投递 的消息、没有定义重试时间而导致内存溢出 55 } 56 var queueFailArgs = new Dictionary<string, object>(); 57 { 58 //暂无 59 } 60 61 //声明队列 62 channel.QueueDeclare(queue: _queueNormal, durable: true, exclusive: false, autoDelete: false, arguments: queueNormalArgs); 63 channel.QueueDeclare(queue: _queueRetry, durable: true, exclusive: false, autoDelete: false, arguments: queueRetryArgs); 64 channel.QueueDeclare(queue: _queueFail, durable: true, exclusive: false, autoDelete: false, arguments: queueFailArgs); 65 66 //为队列绑定交换机 67 channel.QueueBind(queue: _queueNormal, exchange: _exchangeNormal, routingKey: "#"); 68 channel.QueueBind(queue: _queueRetry, exchange: _exchangeRetry, routingKey: "#"); 69 channel.QueueBind(queue: _queueFail, exchange: _exchangeFail, routingKey: "#"); 70 71 #region 创建一个普通消息消费者 72 { 73 var consumer = new EventingBasicConsumer(channel); 74 75 consumer.Received += (sender, e) => 76 { 77 var _sender = (EventingBasicConsumer)sender; //消息传送者 78 var _channel = _sender.Model; //消息传送通道 79 var _message = (BasicDeliverEventArgs)e; //消息传送参数 80 var _headers = _message.BasicProperties.Headers; //消息头 81 var _content = Encoding.UTF8.GetString(_message.Body.ToArray()); //消息内容 82 var _death = default(Dictionary<string, object>); //死信参数 83 84 if (_headers != null && _headers.ContainsKey("x-death")) 85 _death = (Dictionary<string, object>)(_headers["x-death"] as List<object>)[0]; 86 87 try 88 #region 消息处理 89 { 90 Console.WriteLine(); 91 Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t(1.0)消息接收:\r\n\t[deliveryTag={_message.DeliveryTag}]\r\n\t[consumerID={_message.ConsumerTag}]\r\n\t[exchange={_message.Exchange}]\r\n\t[routingKey={_message.RoutingKey}]\r\n\t[content={_content}]"); 92 93 throw new Exception("模拟消息处理失败效果。"); 94 95 //处理成功时 96 Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t(1.1)处理成功:\r\n\t[deliveryTag={_message.DeliveryTag}]"); 97 98 //消息确认 (销毁当前消息) 99 _channel.BasicAck(deliveryTag: _message.DeliveryTag, multiple: false); 100 } 101 #endregion 102 catch (Exception ex) 103 #region 消息处理失败时 104 { 105 var retryCount = (long)(_death?["count"] ?? default(long)); //查询当前消息被重新投递的次数 (首次则为0) 106 107 Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t(1.2)处理失败:\r\n\t[deliveryTag={_message.DeliveryTag}]\r\n\t[retryCount={retryCount}]"); 108 109 if (retryCount >= 2) 110 #region 投递第3次还没消费成功时,就转发给 exchangeFail 交换机 111 { 112 //消息拒绝(投递给死信交换机,也就是上边定义的 ("x-dead-letter-exchange", _exchangeFail)) 113 _channel.BasicNack(deliveryTag: _message.DeliveryTag, multiple: false, requeue: false); 114 } 115 #endregion 116 else 117 #region 否则转发给 exchangeRetry 交换机 118 { 119 var interval = (retryCount + 1) * 10; //定义下一次投递的间隔时间 (单位:秒) 120 //如:首次重试间隔10秒、第二次间隔20秒、第三次间隔30秒 121 122 //定义下一次投递的间隔时间 (单位:毫秒) 123 _message.BasicProperties.Expiration = (interval * 1000).ToString(); 124 125 //将消息投递给 _exchangeRetry (会自动增加 death 次数) 126 _channel.BasicPublish(exchange: _exchangeRetry, routingKey: _message.RoutingKey, basicProperties: _message.BasicProperties, body: _message.Body); 127 128 //消息确认 (销毁当前消息) 129 _channel.BasicAck(deliveryTag: _message.DeliveryTag, multiple: false); 130 } 131 #endregion 132 } 133 #endregion 134 }; 135 channel.BasicConsume(queue: _queueNormal, autoAck: false, consumer: consumer); 136 } 137 #endregion 138 139 #region 创建一个失败消息消费者 140 { 141 var consumer = new EventingBasicConsumer(channel); 142 143 consumer.Received += (sender, e) => 144 { 145 var _message = (BasicDeliverEventArgs)e; //消息传送参数 146 var _content = Encoding.UTF8.GetString(_message.Body.ToArray()); //消息内容 147 148 Console.WriteLine(); 149 Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t(2.0)发现失败消息:\r\n\t[deliveryTag={_message.DeliveryTag}]\r\n\t[consumerID={_message.ConsumerTag}]\r\n\t[exchange={_message.Exchange}]\r\n\t[routingKey={_message.RoutingKey}]\r\n\t[content={_content}]"); 150 }; 151 152 channel.BasicConsume(queue: _queueFail, autoAck: true, consumer: consumer); 153 } 154 #endregion 155 156 Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t正在运行中..."); 157 158 var cmd = default(string); 159 while ((cmd = Console.ReadLine()) != "close") 160 #region 模拟正常消息发布 161 { 162 var msgProperties = channel.CreateBasicProperties(); 163 var msgContent = $"消息内容_{DateTime.Now.ToString("HH:mm:ss.fff")}_{cmd}"; 164 165 channel.BasicPublish(exchange: _exchangeNormal, routingKey: "亚洲.中国.经济", basicProperties: msgProperties, body: Encoding.UTF8.GetBytes(msgContent)); 166 167 Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t发送成功:{msgContent}"); 168 Console.WriteLine(); 169 } 170 #endregion 171 172 Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t正在关闭..."); 173 174 channel.ExchangeDelete(_exchangeNormal); 175 channel.ExchangeDelete(_exchangeRetry); 176 channel.ExchangeDelete(_exchangeFail); 177 channel.QueueDelete(_queueNormal); 178 channel.QueueDelete(_queueRetry); 179 channel.QueueDelete(_queueFail); 180 //channel.Abort(); 181 channel.Close(200, "Goodbye!"); 182 channel.Dispose(); 183 connection.Close(200, "Goodbye!"); 184 connection.Dispose(); 185 186 Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t运行结束。"); 187 Console.ReadKey(); 188 } 189 } 190 }