4.【RabbitMQ实战】- 发布确认
生产者将信道设置成 confirm 模式,一旦信道进入 confirm 模式,所有在该信道上面发布的消
息都将会被指派一个唯一的 ID(从 1 开始),一旦消息被投递到所有匹配的队列之后,broker 就会
发送一个确认给生产者(包含消息的唯一 ID),这就使得生产者知道消息已经正确到达目的队列了,
如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,broker 回传给生产
者的确认消息中 delivery-tag 域包含了确认消息的序列号,此外 broker 也可以设置basic.ack 的
multiple 域,表示到这个序列号之前的所有消息都已经得到了处理。
confirm 模式最大的好处在于他是异步的,一旦发布一条消息,生产者应用程序就可以在等信道
返回确认的同时继续发送下一条消息,当消息最终得到确认之后,生产者应用便可以通过回调方
法来处理该确认消息,如果 RabbitMQ 因为自身内部错误导致消息丢失,就会发送一条 nack 消息,
生产者应用程序同样可以在回调方法中处理该 nack 消息
- 设置队列持久化
- 设置消息持久化
- 发布确认(RabbitMQ 告诉生产者已经确定持久化了)
只有设置了上述三个条件才能保证消息一定不会丢失
发布确认三种模式
单个确认
这是一种简单的确认方式,它是一种同步确认发布的方式,也就是发布一个消息之后只有它被确认发布,后续的消息才能继续布,waitForConfirmsOrDie(long)这个方法只有在消息被确认的时候才返回,如果在指定时间范围内这个消息没有被确认那么它将抛出异常。 这种确认方式有一个最大的缺点就是:发布速度特别的慢,因为如果没有确认发布的消息就会 阻塞所有后续消息的发布,这种方式最多提供每秒不超过数百条发布消息的吞吐量。当然对于某些应用程序来说这可能已经足够了
批量确认
上面那种方式非常慢,与单个等待确认消息相比,先发布一批消息然后一起确认可以极大地提高吞吐量,当然这种方式的缺点就是:当发生故障导致发布出现问题时,不知道是哪个消息出现问题了,我们必须将整个批处理保存在内存中,以记录重要的信息而后重新发布消息。当然这种方案仍然是同步的,也一样阻塞消息的发布。
异步发布确认
处理异步未确认的消息
最好的解决的解决方案就是把未确认的消息放到一个基于内存的能被发布线程访问的队列, 比如说用 ConcurrentQueue 这个队列在 confirm callbacks 与发布线程之间进行消息的传递。
发布确认代码
using rabbitmq.common;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Concurrent;
using System.Text;
namespace PublishConfirm.Producer
{
public class Program
{
static void Main(string[] args)
{
Console.WriteLine("发布确认");
//SinglePublishConfirm(); //单个发布确认耗时:00:00:00.6743354
//MulitPublishConfirm(); //批量发布确认耗时:00:00:00.4526167
PublishConfirmAsync(); // 异步发布确认:00:00:00.3534272
}
/// <summary>
/// 单个发布确认
/// </summary>
public static void SinglePublishConfirm()
{
var begin = DateTime.Now;
using var channel = RabbitmqUntils.GetChannel();
string queueName = Guid.NewGuid().ToString();
channel.QueueDeclare(queue:queueName,durable:false,exclusive:false,autoDelete:false,null);
// 开启发布确认
channel.ConfirmSelect();
var begintime = DateTime.Now;
for (int i = 0; i < 100; i++)
{
var body = Encoding.UTF8.GetBytes($"{i}");
channel.BasicPublish(exchange: "", queueName, false, null, body);
if (channel.WaitForConfirms())// 等待所有消息确认,如果所有的消息都被服务端成功接收返回true,只要有一条没有被成功接收就返回false
{
Console.WriteLine($"消息发送成功:{i}");
}
else
{
//服务端返回 false 或超时时间内未返回,生产者可以消息重发 需添加补救措施
}
}
var end = DateTime.Now;
Console.WriteLine($"单个发布确认耗时:{end - begin}");
}
/// <summary>
/// 批量发布确认
/// </summary>
public static void MulitPublishConfirm()
{
var begin = DateTime.Now;
using var channel = RabbitmqUntils.GetChannel();
string queueName = Guid.NewGuid().ToString();
channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, null);
// 开启发布确认
channel.ConfirmSelect();
var begintime = DateTime.Now;
for (int i = 0; i < 1000; i++)
{
var body = Encoding.UTF8.GetBytes($"{i}");
channel.BasicPublish(exchange: "", queueName, false, null, body);
// 批量确认:100条确认异常
if (i%100 == 0)
{
if (channel.WaitForConfirms())// 等待所有消息确认,如果所有的消息都被服务端成功接收返回true,只要有一条没有被成功接收就返回false
{
Console.WriteLine($"消息发送成功:{i}");
}
else
{
//服务端返回 false 或超时时间内未返回,生产者可以消息重发 需添加补救措施
}
}
}
var end = DateTime.Now;
Console.WriteLine($"批量发布确认耗时:{end - begin}");
}
/// <summary>
/// 异步发布确认
/// </summary>
public static void PublishConfirmAsync()
{
var begin = DateTime.Now;
using var channel = RabbitmqUntils.GetChannel();
string queueName = Guid.NewGuid().ToString();
channel.QueueDeclare(queue: queueName, durable: false, exclusive: false, autoDelete: false, null);
// 开启发布确认
channel.ConfirmSelect();
ConcurrentDictionary<ulong, string> confirmDic = new ConcurrentDictionary<ulong, string>();
var begintime = DateTime.Now;
for (int i = 1; i <= 1000; i++)
{
string msg = $"msg_{i}";
var body = Encoding.UTF8.GetBytes(msg);
confirmDic.TryAdd(channel.NextPublishSeqNo, msg);
channel.BasicPublish(exchange: "", queueName, false, null, body);
}
// 监听确认的消息
channel.BasicAcks += (sender, e) =>
{
Console.WriteLine($"监听确认的消息的序列号:{e.DeliveryTag}");
confirmDic.Remove(e.DeliveryTag, out string body);
Console.WriteLine($"删除确认的消息:{body}");
};
// 监听未确认的消息
channel.BasicNacks += (sender, e) =>
{
Console.WriteLine($"监听未确认的消息序列号:{e.DeliveryTag}");
confirmDic.TryGetValue(e.DeliveryTag, out string body);
Console.WriteLine($"监听未确认的消息:{body}");
};
var end = DateTime.Now;
Console.WriteLine($"异步发布确认:{end - begin}");
}
}
}