RabbitMQ入门教程——工作队列
使用工作队列的好处就是能够并行的处理任务。如果队列中堆积了很多任务,只要添加更多的消费着就可以了,拓展非常方便。
using System.Collections.Generic;
public class TaskQueuesProducer
static int processId = Process.GetCurrentProcess().Id;
Console.WriteLine($"我是生产者{processId}");
var factory = new ConnectionFactory()
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
for (int item = 0; item < 20; item++)
string message = $"我是生产者{processId}发送的消息:{item}";
Console.WriteLine(" Press [enter] to exit.");
using System.Collections.Generic;
public class TaskQueuesConsumer
processId = Process.GetCurrentProcess().Id;
Console.WriteLine($"我是消费者{processId}");
var factory = new ConnectionFactory()
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += Consumer_Received;
//noack=false 不自动消息确认 这时需要手动调用 channel.BasicAck(); 进行消息确认
//noack=true 自动消息确认,当消息被RabbitMQ发送给消费者(consumers)之后,马上就会在内存中移除
channel.BasicConsume(queue: "taskqueue", noAck: false, consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
private static void Consumer_Received(object sender, BasicDeliverEventArgs e)
string message = Encoding.UTF8.GetString(e.Body);
Console.WriteLine($"接收到消息:{message}");
//Thread.Sleep(new Random().Next(1000, 1000 * 5)); //模拟消息处理耗时操作
//对应前面的 BasicConsume 中 noack=false 发送消息确认回执
//EventingBasicConsumer consumer = sender as EventingBasicConsumer;
//consumer.Model.BasicAck(e.DeliveryTag, false);
当我们运行了三个consumer客户端,一个producer客户端后,发现producer发送的20条消息,被三个客户端依次平均接收并处理了。
这是RabbitMQ默认的消息分发机制——轮询( round-robin),默认情况下RabbitMQ会按顺序把消息发送给每个消费者,平均每个消费者都会收到同等数量的消息。
为了防止消息丢失,RabbbitMQ提供了消息确认机制,消费者会通过一个ack,告诉RabbitMQ已经收到并处理了某条消息,然后RabbitMQ就会释放并删除这条消息。
如果consumer挂掉了,没有发送相应,RabbitMQ就会认为消息没有被处理,然后重新发送给其他消费者,这样即使某个consumer挂掉,也不会丢失消息。
消息没有超市的概念,当工作者和它断开连接时,RabbitMQ会重新发送消息,这样在处理耗时较长任务时就不会出现问题了。
之前的代码中我们开启了自动消息确认,这样一旦consumer挂掉,就会发生消息丢失的情况,现在我们来修改两处代码,开启消息确认机制。
channel.BasicConsume(queue: "taskqueue", noAck: false, consumer: consumer);
EventingBasicConsumer consumer = sender as EventingBasicConsumer;
consumer.Model.BasicAck(e.DeliveryTag, false);
注意:一旦忘记消息确认,消息会在你程序推出之后就会重新发送,如果不能释放没响应的消息,RabbitMQ将会占用越来越来越多的内存
可通过以下指令检查忘记确认的消息信息或在 RabbitMQWebweb管理页面中查看
rabbitmqctl list_queues name messages_ready messages_unacknowledged
如果没有特殊的设置,那么在RabbitMQ服务关闭或崩溃的情况下将会丢失所有的队列和消息。为了确保消息不会丢失需要做两个事情,把队列和消息设置为持久化
设置队列为持久化,producer和consumer两处都要修改
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2; //DeliveryMode 消息的投递模式,默认为1 非持久化的,DeliveryMode=2 持久化存储消息内容
- RabbitMQ不允许你使用不同的参数重新定义一个队列,也就是说我们之前定义了taskqueue队列为非持久化,现在再定义为持久化将会返回失败。
- 将消息设置为持久化并不能100%保证消息不会丢失,因为RabbitMQ保存到系统磁盘也需要时间,虽然时间很短,但是确实消耗一定的时间,另外RabbitMQ并不是对每个消息都做fsync,它可能仅仅是保存在cache中,还没来得及保存到磁盘。因此即使我们做了以上几个操作消息持久化的问题还是存在的。如果必须要保证持久化,可以通过使用transaction(事务)来做支持。
RabbitMQ只管分发进入队列的消息,而不关心那些consumer比较繁忙或空闲,这样容易导致一些consumer比较繁忙,一些比较空闲,不能使资源被最大化的使用。
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
执行以上设置之后会发现并没有按照之前的轮询(Round-robin)进行消息转发,而是在消费者不忙时才进行转发。
由于消息并没有发出去,在动态添加了consumer后能够立即投入工作,而默认的轮询转发机制则不支持动态添加消费者,因为此时消息已经分配完毕,无法立即加入工作即使还有很多未完成的任务。
这种方法可能会导致queue满。当然,这种情况下你可能需要添加更多的Consumer,或者创建更多的virtualHost来细化你的设计。
using System.Collections.Generic;
public class TaskQueuesProducer
static int processId = Process.GetCurrentProcess().Id;
Console.WriteLine($"我是生产者{processId}");
var factory = new ConnectionFactory()
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
//autoDelete:自动删除,如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于临时队列。
for (int item = 0; item < 200000; item++)
string message = $"我是生产者{processId}发送的消息:{item}";
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2; //DeliveryMode 消息的投递模式,默认为1 非持久化的,DeliveryMode=2 持久化存储消息内容
Console.WriteLine(" Press [enter] to exit.");
using System.Collections.Generic;
public class TaskQueuesConsumer
processId = Process.GetCurrentProcess().Id;
Console.WriteLine($"我是消费者{processId}");
var factory = new ConnectionFactory()
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
consumer.Received += Consumer_Received;
//noack=false 不自动消息确认 这时需要手动调用 channel.BasicAck(); 进行消息确认
//noack=true 自动消息确认,当消息被RabbitMQ发送给消费者(consumers)之后,马上就会在内存中移除
channel.BasicConsume(queue: "taskqueue", noAck: false, consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
private static void Consumer_Received(object sender, BasicDeliverEventArgs e)
string message = Encoding.UTF8.GetString(e.Body);
Console.WriteLine($"接收到消息:{message}");
Thread.Sleep(new Random().Next(1000, 1000 * 5)); //模拟消息处理耗时操作
//对应前面的 BasicConsume 中 noack=false 发送消息确认回执
EventingBasicConsumer consumer = sender as EventingBasicConsumer;