1 static void Main(string[] args) 2 { 3 const string exchangeName = "clock.exchange"; 4 const string queueName = "clock.queue"; 5 const string routingKey = "clock.queue"; 6 7 var rabbitMqFactory = new ConnectionFactory() 8 { 9 UserName = "**", 10 Password = "**", 11 Port = 5672, 12 VirtualHost = "/", 13 HostName= "localhost", 14 }; 15 rabbitMqFactory.AutomaticRecoveryEnabled = true; 16 var connection = rabbitMqFactory.CreateConnection(); 17 var model = connection.CreateModel(); 18 //交换器声明 19 model.ExchangeDeclare(exchangeName, ExchangeType.Direct); 20 //队列声明 21 model.QueueDeclare(queueName, false, false, false, null); 22 //绑定队列到交换器,可绑定多个队列 23 model.QueueBind(queueName, exchangeName, routingKey, null); 24 25 #region 生产者 26 Task.Run(() => 27 { 28 while (true) 29 { 30 var message = $"Hello World {DateTime.Now.ToString("HH:mm:ss.fff")}"; 31 model.BasicPublish(exchangeName, routingKey, true, null, Encoding.UTF8.GetBytes(message)); 32 Console.WriteLine($"+ 生产了一条消息:{message}"); 33 Thread.Sleep(3000); 34 } 35 }); 36 #endregion 37 38 #region 消费者 39 EventingBasicConsumer consumer = new EventingBasicConsumer(model); 40 model.BasicConsume(queueName, false, consumer); //不自动ack 41 consumer.Received += (ch, ea) => 42 { 43 var msg = Encoding.UTF8.GetString(ea.Body); 44 if (new Random().Next()%2 == 1) 45 {//业务失败 46 Console.WriteLine($"------- 失败"); 47 throw new Exception(); 48 } 49 model.BasicAck(ea.DeliveryTag, false); 50 Console.WriteLine($"------- 消费了一条消息:{msg}"); 51 }; 52 #endregion 53 54 Console.ReadLine(); 55 56 }
代码第44行模拟了业务执行失败的场景,这条消息不会ack,在Queues中一直处于Unacked状态,直到我关闭控制台程序,它才会自动将所有的Unacked的消息全部切换成Ready(虽然不知道它是怎么实现的),从而保证,下一次重启消费端时可继续尝试消费那些"失败"的消息。
那么问题来了,假如我实际业务中,消费端不重启,那么那些Unacked的消息就不会变成Ready状态,就得不到重新处理了,我应该怎么处理这些Unacked的消息?
是不是应该在业务执行失败时,把这条消息存储到另一个队列Q2(专门处理业务失败的消息)同时ack当前队列(确保该消息不同时存在于当前队列和Q2队列,但是这里无法保证)。
EventingBasicConsumer consumer = new EventingBasicConsumer(model); model.BasicConsume(queueName, false, consumer); //不自动ack //启用QoS,每次预取10条,避免消费不过来导致消息堆积在本地缓存 model.BasicQos(0, 10, false); consumer.Received += (ch, ea) => { var msg = Encoding.UTF8.GetString(ea.Body); if (new Random().Next() % 2 == 1) {//模拟消费失败 //方案1:使用BasicNack,将消息重新放回队列重新消费 model.BasicNack(ea.DeliveryTag, false, true); Console.WriteLine($"------- 消费失败,"); //方案2:直接ack并记录异常,后续走其它恢复方案 //代码略... } else {//消费成功 model.BasicAck(ea.DeliveryTag, false); Console.WriteLine($"------- 成功消费了一条消息:{msg}"); } };
model.BasicNack(ea.DeliveryTag, false, true);网络抖动发送失败