RabbitMQ生产者确保消息发送成功,可用于消息最终一致性解决方案,消息确认有两种方式:事务和Confirm
1、事务方式实现保证消息正确发送出去(到达消息服务)
说明:事务会降低消息的吞吐量
static void Main(string[] args)
{
//事务方式
TransactionMode();
//消息确认方式
//ConfirmModel();
Console.ReadLine();
}
/// <summary>
/// 使用事务方式确保数据正确到达消息服务端
/// </summary>
static void TransactionMode()
{
var sign = true;
ConnectionFactory factory = new ConnectionFactory { HostName = "127.0.0.1", UserName = "guest", Password = "guest", VirtualHost = "/" };
using (IConnection conn = factory.CreateConnection())
{
using (IModel im = conn.CreateModel())
{
try
{
im.TxSelect(); //用于将当前channel设置成transaction事务模式
im.ExchangeDeclare("my-exchange", ExchangeType.Direct);
im.QueueDeclare("my-queue", true, false, false, null);
im.QueueBind("my-queue", "my-exchange", "", null);
var properties = im.CreateBasicProperties();
properties.DeliveryMode = 2;
Console.Write("输入发送的内容:");
var msg = Console.ReadLine();
if (msg == "quit")
sign = false;
//for (var i = 1; i <= 100; i++)
//{
// byte[] message = Encoding.UTF8.GetBytes("发送消息:" + i);
// im.BasicPublish("my-exchange", ExchangeType.Direct, properties, message);
// Console.WriteLine("发送消息:" + i);
//}
byte[] message = Encoding.UTF8.GetBytes("发送消息:" + msg);
im.BasicPublish("my-exchange", ExchangeType.Direct, properties, message);
im.TxCommit();//txCommit用于提交事务
}
catch (Exception ex)
{
im.TxRollback();
}
}
}
}
2、Comfirm方式
static void Main(string[] args)
{
//事务方式
//TransactionMode();
//消息确认方式
ConfirmModel();
Console.ReadLine();
}
static void ConfirmModel()
{
ConnectionFactory factory = new ConnectionFactory { HostName = "127.0.0.1", UserName = "guest", Password = "guest", VirtualHost = "/" };
using (IConnection conn = factory.CreateConnection())
{
using (IModel channel = conn.CreateModel())
{
channel.ExchangeDeclare("my-exchange", ExchangeType.Fanout);
channel.QueueDeclare("my-queue", true, false, false, null);
channel.QueueBind("my-queue", "my-exchange", "", null);
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2;
byte[] message = Encoding.UTF8.GetBytes("发送消息!");
//方式1:普通confirm
//NormalConfirm(channel, properties, message);
//方式2:批量confirm
//BatchConfirm(channel, properties, message);
//方式3:异步确认Ack
ListenerConfirm(channel, properties, message);
}
}
}
方式1:普通confirm
/// <summary>
/// 方式1:普通confirm模式
/// 每发送一条消息后,调用waitForConfirms()方法,等待服务器端confirm。实际上是一种串行confirm了。
/// </summary>
/// <param name="channel"></param>
/// <param name="properties"></param>
/// <param name="message"></param>
static void NormalConfirm(IModel channel, IBasicProperties properties, byte[] message)
{
channel.ConfirmSelect();
channel.BasicPublish("my-exchange", ExchangeType.Fanout, properties, message);
if (!channel.WaitForConfirms())
{
Console.WriteLine("send message failed.");
}
Console.WriteLine("send message success.");
}
方式2:批量confirm
/// <summary>
/// 方式2:批量confirm模式
/// 每发送一批消息后,调用waitForConfirms()方法,等待服务器端confirm。
/// </summary>
/// <param name="channel"></param>
/// <param name="properties"></param>
/// <param name="message"></param>
static void BatchConfirm(IModel channel, IBasicProperties properties, byte[] message)
{
channel.ConfirmSelect();
for (int i = 0; i < 10; i++)
{
channel.BasicPublish("my-exchange", ExchangeType.Fanout, properties, message);
}
if (!channel.WaitForConfirms())
{
Console.WriteLine("send message failed.");
}
Console.WriteLine("send message success.");
channel.Close();
}
方式3:异步回调方式确认Ack
/// <summary>
/// 使用异步回调方式监听消息是否正确送达
/// </summary>
/// <param name="channel"></param>
/// <param name="properties"></param>
/// <param name="message"></param>
static void ListenerConfirm(IModel channel, IBasicProperties properties, byte[] message)
{
channel.ConfirmSelect();//开启消息确认模式
/*-------------Return机制:不可达的消息消息监听--------------*/
//这个事件就是用来监听我们一些不可达的消息的内容的:比如某些情况下,如果我们在发送消息时,当前的exchange不存在或者指定的routingkey路由不到,这个时候如果要监听这种不可达的消息,就要使用 return
EventHandler<BasicReturnEventArgs> evreturn = new EventHandler<BasicReturnEventArgs>((o, basic) =>
{
var rc = basic.ReplyCode; //消息失败的code
var rt = basic.ReplyText; //描述返回原因的文本。
var msg = Encoding.UTF8.GetString(basic.Body.Span); //失败消息的内容
//在这里我们可能要对这条不可达消息做处理,比如是否重发这条不可达的消息呀,或者这条消息发送到其他的路由中等等
//System.IO.File.AppendAllText("d:/return.txt", "调用了Return;ReplyCode:" + rc + ";ReplyText:" + rt + ";Body:" + msg);
Console.WriteLine("send message failed,不可达的消息消息监听.");
});
channel.BasicReturn += evreturn;
//消息发送成功的时候进入到这个事件:即RabbitMq服务器告诉生产者,我已经成功收到了消息
EventHandler<BasicAckEventArgs> BasicAcks = new EventHandler<BasicAckEventArgs>((o, basic) =>
{
Console.WriteLine("send message success,Acks.");
//System.IO.File.AppendAllText("d:/ack.txt", "\r\n调用了ack;DeliveryTag:" + basic.DeliveryTag.ToString() + ";Multiple:" + basic.Multiple.ToString() + "时间:" + DateTime.Now.ToString());
});
//消息发送失败的时候进入到这个事件:即RabbitMq服务器告诉生产者,你发送的这条消息我没有成功的投递到Queue中,或者说我没有收到这条消息。
EventHandler<BasicNackEventArgs> BasicNacks = new EventHandler<BasicNackEventArgs>((o, basic) =>
{
//MQ服务器出现了异常,可能会出现Nack的情况
Console.WriteLine("send message fail,Nacks.");
//System.IO.File.AppendAllText("d:/nack.txt", "\r\n调用了Nacks;DeliveryTag:" + basic.DeliveryTag.ToString() + ";Multiple:" + basic.Multiple.ToString() + "时间:" + DateTime.Now.ToString());
});
channel.BasicAcks += BasicAcks;
channel.BasicNacks += BasicNacks;
channel.BasicPublish("my-exchange", ExchangeType.Fanout, properties, message);
}