首页新闻找找看学习计划

RabbitMQ 业务失败产生的Unacked该如何处理?

0
悬赏园豆:10 [已解决问题] 解决于 2018-06-22 16:23
 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队列,但是这里无法保证)。

找不到一个满意的昵称的主页 找不到一个满意的昵称 | 菜鸟二级 | 园豆:286
提问于:2018-06-21 09:49
< >
分享
最佳答案
0
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}");
    }
};
找不到一个满意的昵称 | 菜鸟二级 |园豆:286 | 2018-06-22 16:22

修正:

QoS必须在EventingBasicConsumer 之前
//启用QoS,每次预取10条,避免消费不过来导致消息堆积在本地缓存
model.BasicQos(0, 2, false);

EventingBasicConsumer consumer = new EventingBasicConsumer(model); model.BasicConsume(queueName, false, consumer); 

找不到一个满意的昵称 | 园豆:286 (菜鸟二级) | 2018-06-25 11:58
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册