RabbitMQ--工作队列
1、工作队列:用来将耗时的任务分发给多个消费者。一个消息只能被一个消费者获取。该模式的主要思想是:避免立即执行资源密集型、且必须等待其完成的任务,而是安排稍后完成任务。
2、涉及到的问题:
2.1、 消息如何分配:多个接收端接收同一个Queue时,采用了Round-robin分配算法,即轮叫调度——依次分配给各个接收方。例如,在有两个 Worker 的情况下,假设所有奇数消息都很庞大、偶数消息都很轻量,那么一个 Worker 将会一直忙碌,而另一个 Worker 几乎不做任何工作。我们可以使用参数设置prefetchCount = 1
的basicQos
方法。这就告诉 RabbitMQ 同一时间不要给一个 Worker 发送多条消息。或者换句话说,不要向一个 Worker 发送新的消息,直到它处理并确认了前一个消息。相反,它会这个消息调度给下一个不忙碌的 Worker。
// 告知 RabbitMQ,在未收到当前 Worker 的消息确认信号时,不再分发给消息,确保公平调度。 channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
2.2、消息确认
a、为了确保消息永远不会丢失,RabbitMQ 支持 消息确认 机制。消费者回发一个确认信号 Ack(nowledgement) 给 RabbitMQ,告诉它某个消息已经被接收、处理并且可以自由删除它。
b、如果一个消费者在还没有回发确认信号之前就挂了(其通道关闭,连接关闭或者 TCP 连接丢失),RabbitMQ 会认为该消息未被完全处理,并将其重新排队。
c、如果有其他消费者同时在线,该消息将会被会迅速重新分发给其他消费者。这样,即便 Worker 意外挂掉,也可以确保消息不会丢失。
d、没有任何消息会超时;当消费者死亡时,RabbitMQ 将会重新分发消息。即使处理消息需要非常非常长的时间也没关系。
e、默认开启了消息确认(接收方接收到消息后,立即向服务器发回确认)。消息接收方处理完消息后,向服务器发送消息确认,服务器再删除该消息。对于耗时的work,可以先关闭自动消息确认,在work完成后,再手动发回确认。
channel.BasicConsume(queue: "task_queue", noAck: false,consumer: consumer); channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
3、持久化:当 RabbitMQ 退出或崩溃时,它会忘记已存在的队列和消息,除非告诉它不要这样做。为了确保消息不会丢失,有两件事是必须的:我们需要将队列和消息标记为持久。
// 声明队列,标记为持久性。durable:true channel.QueueDeclare(queue: "task_queue", durable: true,exclusive: false, autoDelete: false,arguments: null); // 将消息标记为持久性。 var properties = channel.CreateBasicProperties(); properties.Persistent = true;
4.代码:
send:
public Send() { var factory = new ConnectionFactory() { HostName = "localhost" }; using (var connection = factory.CreateConnection()) using (var channel = connection.CreateModel()) { // 声明队列,标记为持久性。durable:true channel.QueueDeclare(queue: "task_queue", durable: true, exclusive: false, autoDelete: false, arguments: null); // 将消息标记为持久性。 var properties = channel.CreateBasicProperties(); properties.Persistent = true; for (var i = 0; i < 50; i++) { var message = $"{i + 1}helloworld{DateTime.Now.ToString()}"; var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish(exchange: "", routingKey: "task_queue", basicProperties: properties, body: body); Console.WriteLine(" Sender Sent {0}", message); } } }
receive:
public Receive() { var factory = new ConnectionFactory() { HostName = "localhost" }; using (var connection = factory.CreateConnection()) using (var channel = connection.CreateModel()) { // 声明队列,标记为持久性。durable:true channel.QueueDeclare(queue: "task_queue", durable: true, exclusive: false, autoDelete: false, arguments: null); // 告知 RabbitMQ,在未收到当前 Worker 的消息确认信号时,不再分发给消息,确保公平调度。 channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false); Console.WriteLine(" [*] Waiting for messages."); // 构建消费者实例。 var consumer = new EventingBasicConsumer(channel); // 绑定消息接收事件。 consumer.Received += (model, ea) => { var body = ea.Body; var message = Encoding.UTF8.GetString(body); Console.WriteLine(" [x] Received {0}", message); // 模拟耗时操作。 int dots = message.Split('.').Length - 1; Thread.Sleep(3000); Console.WriteLine(" [x] Done"); // 手动发送消息确认信号。 channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false); }; channel.BasicConsume(queue: "task_queue", noAck: false, consumer: consumer); Console.ReadLine(); } }