1:消息确认
消息确认有两种方式 a)事务 b)confirm 我就直接写一起了
public static void SendMessage(string confirm)
{
//创建工厂对象
var factory = new ConnectionFactory()
{
HostName = "192.168.1.11",
Port = 5672,
UserName = "fanlin",
Password = "admin",
VirtualHost = "/" //虚拟主机
};
//创建连接
using (var conn = factory.CreateConnection())
{
//string queueName = "publishQueue";
string exchangeName = "tx-exchange";
string queueName = "tx-queue";
string routekey = "directkey";
//创建信道
using (var channel = conn.CreateModel())
{
Console.WriteLine(channel);
//用于将当前channel设置成transaction模式
//下面有提交 TxCommit
//channel.TxSelect();
//声明交换机
channel.ExchangeDeclare(exchangeName, type: "direct");
//durable:持久化
//exclusive:设置是否排他。为 true 则设置队列为排他的。如果一个队列被声明为排 他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除
//autoDelete:自动删除
var queue = channel.QueueDeclare(queueName, durable: false, exclusive: false, autoDelete: false, arguments: null);
//声明队列的时候设置最大优先级,生产者和消费者都要设置
// var queue = channel.QueueDeclare(queueName, durable: false, exclusive: false, autoDelete: false,arguments: new Dictionary<string, object> { { "x-max-priority", 10 } });
//可以在生产者里面绑定消费者,以免先启动了消费者而生产者未启动无法绑定的情况
channel.QueueBind(queueName, exchangeName, routekey, null);
switch (confirm)
{
case "tx":
channel.TxSelect();
break;
case "cf":
channel.ConfirmSelect();
//发送消息
//确认
channel.WaitForConfirms();
break;
}
channel.TxSelect();
while (true)
{
try
{
Console.WriteLine("Please Input Message:");
string message = Console.ReadLine();
if (message.Equals("exit"))
{
Console.WriteLine("Now Exit");
break;
}
var body = Encoding.UTF8.GetBytes(message);
//下面两种都可以将消息标记为持久化
var properties = channel.CreateBasicProperties();
properties.Persistent = true;
//properties.DeliveryMode = 2;
//设置优先级
properties.Priority = 9;//优先级 0-9
channel.BasicPublish(exchange: exchangeName,
routingKey: routekey,
basicProperties: null,
body: body);
Console.WriteLine("Already Sent {0}", message);
channel.TxCommit();
}
//异常回滚
catch (Exception ex)
{
if (channel.IsOpen)
{
Console.WriteLine("触发事务回滚");
channel.TxRollback();
}
}
}
//Console.WriteLine(" Press [enter] to exit.");
//Console.ReadLine();
}
}
}
2:死信队列和延迟队列
死信队列就是消息没有被消费
延迟队列其实就是死信队列,比如电商业务中,订单消息30分钟确认,就是延迟队列的实现,而延迟队列就理解成从死信队列里面读取数据
public static void SendMessage()
{
//创建工厂对象
var factory = new ConnectionFactory()
{
HostName = "192.168.1.6",
Port = 5672,
UserName = "fanlin",
Password = "admin",
VirtualHost = "/" //虚拟主机
};
//创建连接
using (var conn = factory.CreateConnection())
{
//消息队列
string normalEx = "directExcheange";
string normalQueue = "normalQueue";
string normalRoute = "routeA";
//死信队列
string dlxExchange = "dlxExchange";
string dlxQueue = "dlxQueue";
string dlxRoute = "routeD";
//创建信道
using (var channel = conn.CreateModel())
{
Console.WriteLine(channel);
//durable:持久化
//exclusive:设置是否排他。为 true 则设置队列为排他的。如果一个队列被声明为排 他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除
//autoDelete:自动删除
// var queue = channel.QueueDeclare(queueName, durable: false, exclusive: false, autoDelete: true, arguments: null);
//死信交换机
channel.ExchangeDeclare(dlxExchange, type: "direct");
var queue2 = channel.QueueDeclare(dlxQueue, durable: false, exclusive: false, autoDelete: false, arguments: null);
channel.QueueBind(dlxQueue, dlxExchange, dlxRoute);
//正常交换机、队列
//延迟队列其实就是死信队列,只是需要设置一个队列的过期时间
//死信队列:
// 1:消息被拒绝(Basic.Reject或Basic.Nack)并且设置 requeue 参数的值为 false
//2:消息过期了
//3: 队列达到最大的长度
//延迟队列可以理解为读消息从死信队列里面读取
// 需求:用户在系统中创建一个订单,如果10s后,用户没有进行支付,那么自动取消订单。
//分析:
// 1、上面这个情况,我们就适合使用延时队列来实现,那么延时队列如何创建
// 2、延时队列可以由 过期消息+死信队列 来时间
// 3、过期消息通过队列中设置 x-message - ttl 参数实现
// 4、死信队列通过在队列申明时,给队列设置 x-dead - letter - exchange 参数,然后另外申明一个队列绑定x - dead - letter - exchange对应的交换器。
//dic.Add("x-expires", 30000); // 30秒后队列自动干掉
//dic.Add("x-message-ttl", 12000);//队列上消息过期时间,应小于队列过期时间
channel.ExchangeDeclare(normalEx, type: "direct");
var queue1 = channel.QueueDeclare(normalQueue, durable: false, exclusive: false, autoDelete: false, arguments: new Dictionary<string, object> {
{ "x-dead-letter-exchange",dlxExchange}, //设置当前队列的DLX
{ "x-dead-letter-routing-key",dlxRoute}, //设置DLX的路由key,DLX会根据该值去找到死信消息存放的队列
});
channel.QueueBind(normalQueue, normalEx, normalRoute);
while (true)
{
Console.WriteLine("Please Input Message:");
string message = Console.ReadLine();
if (message.Equals("exit"))
{
Console.WriteLine("Now Exit");
break;
}
var body = Encoding.UTF8.GetBytes(message);
var properties = channel.CreateBasicProperties();
properties.Persistent = true;
channel.BasicPublish(exchange: normalEx,
routingKey: normalRoute,
basicProperties: properties,
body: body);
Console.WriteLine($"Already Sent ==>{normalRoute}: {message}");
}
//Console.WriteLine(" Press [enter] to exit.");
//Console.ReadLine();
}
}
}
3:备份交换机
备份交换器是为了实现没有路由到队列的消息,声明交换机的时候添加属性alternate-exchange,声明一个备用交换机,一般声明为fanout类型,这样交换机收到路由不到队列的消息就会发送到备用交换机绑定的队列中。
public void SendMsg()
{
string queueName = "BACKUP_QUEUE";
string exchangeName = "BACKUP_EXCHANGE";
string backupQueue = "BACKUP_QUEUE_D";
string backupExchangeName = "ALTERNATE_EXCHNAGE";
string routeKey = "BACKUP_ROUTEKEY";
//创建工厂对象
var factory = new ConnectionFactory()
{
HostName = "192.168.1.6",
Port = 5672,
UserName = "fanlin",
Password = "admin",
VirtualHost = "/" //虚拟主机
};
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
// 声明交互交换机[指定备份交换机]
Dictionary<string, object> argument = new Dictionary<string, object>();
argument.Add("alternate-exchange", backupExchangeName);
channel.ExchangeDeclare(exchangeName, ExchangeType.Direct, false, false, argument);
// 声明备份交互机
channel.ExchangeDeclare(backupExchangeName, ExchangeType.Fanout, false, false, null);
// 声明队列
channel.QueueDeclare(queueName, false, false, false, null);
// 声明备份队列
channel.QueueDeclare(backupQueue, false, false, false, null);
// 绑定队列
channel.QueueBind(queueName, exchangeName, routeKey);
channel.QueueBind(backupQueue, backupExchangeName, "");
//发送数据
for (int i = 0; i < 10; i++)
{
// 消息内容
string message = "This is Backup Exchange Model-->" + i;
var body = Encoding.UTF8.GetBytes(message);
//指定发送消息到哪个路由,以及他的路由键,消息等
if (i % 2 == 0)
{
channel.BasicPublish(exchangeName, routeKey, null, body);
}
else
{
//匹配不到队列[如果路由key找不到队列则启用备用交换机]
channel.BasicPublish(exchangeName, "kkkk", null, body);
}
Console.WriteLine(" [x] Sent '" + message + "'");
Thread.Sleep(200);
}
}
}
}
4:消息可靠性
从消费者的角度来说,服务器向消费者发送消息,消费者可以反馈,那么在服务器怎么确定呢,有个mandatory参数,然后可以添加一个事件来设置
public void Producer()
{
string queueName = "RE_QUEUE";
string exchangeName = "RE_EXCHANGE";
using (var connection = RabbitMQHelper.GetConnection("192.168.3.200", 5671))
{
using (var channel = connection.CreateModel())
{
// 声明队列
channel.QueueDeclare(queueName, false, false, false, null);
// 声明交换机[交换机没有绑定队列的情况]
//channel.ExchangeDeclare(exchangeName, ExchangeType.Fanout);
//channel.QueueBind(queueName, exchangeName, "");
// 声明交换机
channel.ExchangeDeclare(exchangeName, ExchangeType.Direct);
channel.QueueBind(queueName, exchangeName, "test_direct");
string message = "This is Return Model";
var body = Encoding.UTF8.GetBytes(message);
// 配置回调
channel.BasicReturn += (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+"\r\n");
Console.WriteLine("send message failed,不可达的消息消息监听.");
};
var properties = channel.CreateBasicProperties();
properties.MessageId = "fdsfdfs";
channel.BasicPublish(exchange: exchangeName,
routingKey: "test_direct1",
mandatory: true, // 必须设置该参数为true
basicProperties: properties,
body: body);
Console.WriteLine(" [x] Sent {0}", message);
}
}
}