【问题标题】:How to utilize prefetch count for consumer with .NET RabbitMQ client如何使用 .NET RabbitMQ 客户端为消费者使用预取计数
【发布时间】:2019-07-02 10:55:47
【问题描述】:

它在 RabbitMQ 文档中声明如下

“根据经验,在线程之间共享 Channel 实例是 要避免的事情。应用程序应该更喜欢使用通道 每个线程而不是跨多个共享相同的通道 线程。”

目前我们正在查看预取计数,建议如果您有少量消费者并且 autoack=false,那么我们应该一次消费许多消息。但是,我们发现如果消费者使用单个执行线程发回手动确认,则预取无效。但是,如果我们将消费者处理包装在一个任务中,我们会发现预取计数确实很重要,并且大大提高了消费者的性能。

请参阅以下示例,其中我们将消费者对消息的消费包装在 Task 对象中:

class Program
{
    public static void Main()
    {
        var factory = new ConnectionFactory()
        {
            HostName = "172.20.20.13",
            UserName = "billy",
            Password = "guest",
            Port = 5671,
            VirtualHost = "/",
            Ssl = new SslOption
            {
                Enabled = true,
                ServerName = "rabbit.blah.com",
                Version = System.Security.Authentication.SslProtocols.Tls12
            }
        };
        var connection = factory.CreateConnection();
        var channel = connection.CreateModel();
        channel.BasicQos(0, 100, false);
        channel.ExchangeDeclare(exchange: "logs", type: "fanout");
        var queueName = channel.QueueDeclare().QueueName;
        Console.WriteLine(" [*] Waiting for logs.");

        var consumer = new EventingBasicConsumer(channel);
        consumer.Received += (model, ea) =>
        {
            var _result = new Task(() => {
                var body = ea.Body;
                var message = Encoding.UTF8.GetString(body);
                System.Threading.Thread.Sleep(80);

                channel.BasicAck(ea.DeliveryTag, false);
            });
            _result.Start();
        };
        channel.BasicConsume(queue: "test.queue.1", autoAck: false, consumer: consumer);

        Console.WriteLine(" Press [enter] to exit.");
        Console.ReadLine();
    }
}

我的问题是人们如何使用 .NET rabbitmq 客户端实现消费者,利用预取计数?,你必须使用某种任务手动确认吗?,它安全吗

【问题讨论】:

  • 就我个人而言,我讨厌 RabbitMQ API,在此过程中您会发现更多的错误(例如,即使您在选项中说您需要一条一条地使用消息,它仍然会消耗多条消息!该死!) .最好使用一些消息总线 API,如 MassTransit。它隐藏了可能导致死锁的有趣的通道操作、队列管理和连接损坏。它还支持模拟 rabbitmq 进行测试。
  • @eocron - RabbitMQ .NET 客户端是open source,我们(RabbitMQ 团队)定期收到拉取请求以修复错误和改进代码。与其声称堆栈溢出,不如为项目贡献您的专业知识?
  • @LukeBakken 是通过使用任务发回确认来利用预取计数性能优势的唯一方法吗?

标签: c# rabbitmq messaging


【解决方案1】:

来源:https://www.rabbitmq.com/api-guide.html

使用手动确认时,重要的是要考虑 什么线程进行确认。如果它不同于 接收交付的线程(例如 Consumer#handleDelivery 将交付处理委托给不同的线程),确认 将多个参数设置为 true 是不安全的,将导致 双重确认,因此是通道级协议 关闭通道的异常。确认单个消息 时间可以是安全的。

channel.basicAck(tag, false) 是线程安全的

consumerChannel.basicAck(tag, true) 不是。

RabbitMQ and channels Java thread safety也提到了一些优点

【讨论】:

  • 那是Java指南,问这个问题的人使用的是.NET。库之间存在差异。
【解决方案2】:

您所参考的文档适用于 Java 客户端。你应该指的是this document

您正在使用最新版本的 .NET 客户端 (5.1),因此在 Received 事件处理程序中执行您的工作不会阻塞处理 TCP 数据的其他线程,也不会阻塞心跳- 两者都很好。

首先,调用channel.BasicQos(0, 1, false) 意味着您的消费者一次只能从RabbitMQ 接收一条就绪消息,并且在调用BasicAck 之前不会传递另一条消息。所以,真的没有理由在另一个线程中做你的工作,因为无论如何你都不会收到另一条消息。

如果您增加预取值(通过实验和运行基准测试),如果您的工作运行时间超过几毫秒,您将不得不在后台线程中完成工作。

当您在Received 事件回调中执行您的工作时,它将阻塞用于进行该回调的线程,因为回调不是在其自己的线程上执行的。因此,您可以确保您的工作非常简短,或者在另一个线程中完成工作。

我刚刚花了一些时间查看 .NET 客户端代码,我很确定 IModel 实例不是线程安全的。如果增加预取,您将有机会同时确认多条消息,因此我建议实施一个使用它的解决方案,并确保在创建连接的同一线程上调用 BasicAck


注意:RabbitMQ 团队会监控 rabbitmq-users mailing list,并且有时只回答 StackOverflow 上的问题。

【讨论】:

  • 感谢您的回复和道歉,我已将示例更新为预取计数等于 100。(这是一个错字)
  • 这是否意味着您建议我们使用相同的执行线程来执行BasicAck,但将multiple设置为true?
  • @LukeBakken “我建议实施......确保在创建连接的同一线程上调用 BasicAck。”您的意思是在创建通道的同一个线程上说,还是真的在创建连接的线程上说?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-21
  • 1970-01-01
  • 2019-04-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多