目录
一、RabbitMQ 队列模型
1. 简单队列
说明:
P:消息的生产者
C:消息的消费者
红色:队列
生产者将消息发送到队列,消费者从队列中获取消息。
2.Work模式
说明:
一个生产者、2个消费者。
一个消息只能被一个消费者获取。
生产者代码:
using System;
using RabbitMQ.Client;
using System.Text;
namespace RabbitMQDemo.Producter
{
class Program
{
static string QueueName = "task_queue";
static IConnection connection;
static void Main(string[] args)
{
var factory = new ConnectionFactory() { HostName = "192.168.1.11", UserName = "user", Password = "user123" };
connection = factory.CreateConnection();
Run();
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
private static void Run()
{
while (true)
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: QueueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
var message = GetMessage(DateTime.Now.ToString("HH:mm:ss"));
var body = Encoding.UTF8.GetBytes(message);
var properties = channel.CreateBasicProperties();
properties.Persistent = true;
channel.BasicPublish(exchange: "",
routingKey: QueueName,
basicProperties: properties,
body: body);
Console.WriteLine(" [x] Sent {0}", message);
Console.ReadLine();
}
}
}
private static string GetMessage(string args)
{
return "Hello World! " + args;
}
}
}
消费者代码:
using System;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Threading;
namespace RabbitMQDemo.Worker
{
class Program
{
static string QueueName = "task_queue";
static void Main(string[] args)
{
var factory = new ConnectionFactory() { HostName = "192.168.1.11", UserName="user", Password="user123" };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: QueueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
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);
channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
};
channel.BasicConsume(queue: QueueName,
autoAck: false,
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
}
}
}
3.订阅模式
说明:
1、1个生产者,多个消费者
2、每一个消费者都有自己的一个队列
3、生产者没有将消息直接发送到队列,而是发送到了交换机
4、每个队列都要绑定到交换机
5、生产者发送的消息,经过交换机,到达队列,实现,一个消息被多个消费者获取的目的
注意:一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费
4.路由模式
说明:
交换机可以绑定一个或多个队列
生产者代码:
当交换机没有绑定队列时,消息会直接丢弃。
using System;
using RabbitMQ.Client;
using System.Text;
namespace RabbitMQDemo.Producter
{
class Program
{
static void Main(string[] args)
{
new RoutingProducter().Run();
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
}
public class RoutingProducter
{
static string ExchangeName = "logs";
static IConnection connection;
public void Run()
{
var factory = new ConnectionFactory() { HostName = "192.168.1.11", UserName = "user", Password = "user123" };
connection = factory.CreateConnection();
while (true)
{
using (var channel = connection.CreateModel())
{
//声明交换机
channel.ExchangeDeclare(ExchangeName, "direct");
string routingKey = new Random().Next(0, 2) == 0 ? "error" : "info";
string message = routingKey == "error" ? "系统异常" : "请求执行成功";
var body = Encoding.UTF8.GetBytes(message);
//发送消息到交换机
channel.BasicPublish(exchange: ExchangeName,
routingKey: routingKey,
basicProperties: null,
body: body);
Console.WriteLine(" [x] Sent '{0}':'{1}'", routingKey, message);
}
Console.ReadLine();
}
}
}
}
消费者代码:
声明的队列没有持久化,程序停止后,绑定队列会自动删除。
using System;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace RabbitMQDemo.Worker
{
class Program
{
static void Main(string[] args)
{
new RoutingWorker().Run();
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
}
public class RoutingWorker
{
static string ExchangeName = "logs";
static string RoutingError = "error";
static string RoutingInfo = "info";
static IConnection connection;
public void Run()
{
var factory = new ConnectionFactory() { HostName = "192.168.1.11", UserName = "user", Password = "user123" };
connection = factory.CreateConnection();
using (var channel = connection.CreateModel())
{
//声明交换机
channel.ExchangeDeclare(ExchangeName, "direct");
//声明队列
channel.QueueDeclare("logs_queue");
//将队列绑定到交换机
channel.QueueBind("logs_queue", ExchangeName, RoutingInfo);
channel.QueueBind("logs_queue", ExchangeName, RoutingError);
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}':'{1}'",
ea.RoutingKey, message);
};
channel.BasicConsume(queue: "logs_queue",
autoAck: true,
consumer: consumer);
Console.ReadLine();
}
}
}
}
5.主题模式(通配符模式)
同一个消息被多个消费者获取。一个消费者队列可以有多个消费者实例,只有其中一个消费者实例会消费到消息。
二、RabbitMQ 相关知识
1. 消息分发机制
轮询分发 :使用任务队列的优点之一就是可以轻易的并行工作。如果我们积压了好多工作,我们可以通过增加工作者(消费者)来解决这一问题,使得系统的伸缩性更加容易。在默认情况下,RabbitMQ将逐个发送消息到在序列中的下一个消费者(而不考虑每个任务的时长等等,且是提前一次性分配,并非一个一个分配)。平均每个消费者获得相同数量的消息。这种方式分发消息机制称为Round-Robin(轮询)。
公平分发 :虽然上面的分配法方式也还行,但是有个问题就是:比如:现在有2个消费者,所有的奇数的消息都是繁忙的,而偶数则是轻松的。按照轮询的方式,奇数的任务交给了第一个消费者,所以一直在忙个不停。偶数的任务交给另一个消费者,则立即完成任务,然后闲得不行。而RabbitMQ则是不了解这些的。这是因为当消息进入队列,RabbitMQ就会分派消息。它不看消费者为应答的数目,只是盲目的将消息发给轮询指定的消费者。
2. 消息的确认模式
消费者从队列中获取消息,服务端如何知道消息已经被消费呢?
模式1:自动确认
只要消息从队列中获取,无论消费者获取到消息后是否成功消息,都认为是消息已经成功消费。
模式2:手动确认
消费者从队列中获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,如果消费者一直没有反馈,那么该消息将一直处于不可用状态。
3. Ack,Nack,Reject的关系
1. 消息处理成功,执行Ack,RabbitMQ会把消息从队列中删除。
2. 消息处理失败,执行Nack或者Reject:
a) 当requeue=true时,消息会重新回到队列,然后当前消费者会马上再取回这条消息;
b) 当requeue=false时,如果Exchange有设置Dead Letter Exchange,则消息会去到Dead Letter Exchange;
c) 当requeue=false时,如果Exchange没设置Dead Letter Exchange,则消息从队列中删除,效果与Ack相同。
3. Nack与Reject的区别在于:Nack可以批量操作,Reject只能单条操作。
三、RabbitMQ简单封装
Program.cs
using System;
namespace RabbitMQ
{
class Program
{
static void Main(string[] args)
{
MQHelperFactory.Default().Receive("EasyNetQ_Default_Error_Queue", item =>
{
MQHelperFactory.Default().SendMsg<string>("fanout_queue_default", item);
});
Console.ReadKey();
}
}
}
MQHelperFactory.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace RabbitMQ
{
public class MQHelperFactory
{
public static RabbitMQHelper Default() =>
new RabbitMQHelper("exchange_fanout");
}
}
RabbitMQHelper.cs
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Collections.Generic;
using System.Text;
namespace RabbitMQ
{
public class RabbitMQHelper
{
ConnectionFactory connectionFactory;
IConnection connection;
IModel channel;
string exchangeName;
public RabbitMQHelper(string changeName= "fanout_mq") {
this.exchangeName = changeName;
//创建连接工厂
connectionFactory = new ConnectionFactory
{
HostName = "192.168.8.203",
UserName = "superrd",
Password = "superrd"
};
//创建连接
connection = connectionFactory.CreateConnection();
//创建通道
channel = connection.CreateModel();
//声明交换机
channel.ExchangeDeclare(exchangeName, ExchangeType.Topic);
}
/// <summary>
/// 发送消息
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="queName"></param>
/// <param name="msg"></param>
public void SendMsg<T>(string queName,T msg)where T:class
{
//声明一个队列
channel.QueueDeclare(queName, true, false, false, null);
//绑定队列,交换机,路由键
channel.QueueBind(queName, exchangeName, queName);
var basicProperties = channel.CreateBasicProperties();
//1:非持久化 2:可持久化
basicProperties.DeliveryMode = 2;
var payload = Encoding.UTF8.GetBytes("我发出的消息");
var address = new PublicationAddress(ExchangeType.Direct, exchangeName, queName);
channel.BasicPublish(address, basicProperties, payload);
}
/// <summary>
/// 消费消息
/// </summary>
/// <param name="queName"></param>
/// <param name="received"></param>
public void Receive(string queName,Action<string> received)
{
//事件基本消费者
EventingBasicConsumer consumer = new EventingBasicConsumer(channel);
//接收到消息事件
consumer.Received += (ch, ea) =>
{
string message = Encoding.UTF8.GetString(ea.Body);
received(message);
//确认该消息已被消费
channel.BasicAck(ea.DeliveryTag, false);
};
//启动消费者 设置为手动应答消息
channel.BasicConsume(queName, false, consumer);
}
}
}