之前的博客我们实现了RabbitMQ在Windows系统下的安装和使用,并且通过对队列的使用实现了简单的发送和接收功能。本部分主要介绍RabbitMQ的发布/订阅以及路由设置,主要实现以下功能。
实现步骤:
一:发布/订阅模式理论知识
1、转发器(Exchanges)
RabbitMQ消息模型的核心理念是生产者永远不会直接发送任何消息给队列,一般的情况生产者甚至不知道消息应该发送到哪些队列。相反的,生产者只能发送消息给转发器(Exchange)。转发器是非常简单的,一边接收从生产者发来的消息,另一边把消息推送到队列中。转发器必须清楚的知道消息如何处理它收到的每一条消息。是追加到一个指定的队列、追加到指定队列、或者扔掉,这些规则通过转发器的类型进行定义。
常用转发器及解释:
fanout:生产者发送到exchange的消息会被所有的消费者接受处理。
direct:生产者在发送消息至转换器时,会指定一个路由key,消费者消费的时候也会指定一个路由key,这样你发送的消息指定的是什么routingKey,那么转发器就会把消息转给相应队列对应的消费者进行处理。
topic:比direct类型更加灵活,提供.与*的匹配模式,让路由的绑定key与消费者的选择key更加强大。其中,.号可以匹配一个标识符,*号可以配置0个或多个标识符。
2、匿名转发器(nameless exchange)
前面说到生产者只能发送消息给转发器(Exchange),但是我们前两篇博客中的例子并没有使用到转发器,我们仍然可以发送和接收消息。这是因为我们使用了一个默认的转发器,它的标识符为””。之前发送消息的代码:
channel.BasicPublish("",QUEUE_NAME,MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
第一个参数为转发器的名称,我们设置为””:如果存在routingKey(第二个参数),消息由routingKey决定发送到哪个队列。
现在我们可以指定消息发送到的转发器:
channel.BasicPublish( Exchange_Name,"", null, message.getBytes());
3、临时队列(Temporary queues)
前面的博客中我们都为队列指定了一个特定的名称。能够为队列命名对我们来说是很关键的,我们需要指定消费者为某个队列。当我们希望在生产者和消费者间共享队列时,为队列命名是很重要的。不过,对于我们很多系统中并不关心队列的名称。我们想要接收到所有的消息,而且我们也只对当前正在传递的数据的感兴趣。为了满足我们的需求,需要做两件事:
第一,无论什么时间连接到RabbitMQ我们都需要一个新的空的队列。我们可以随机创建队列,或者让服务器给我们提供一个随机队列的名称。
第二,一旦消费者与Rabbit断开,消费者所接收的那个队列应该被自动删除。
.Net中我们可以使用QueueDeclare()方法,不传递任何参数,来创建一个非持久的、唯一的、自动删除的队列且队列名称由服务器随机产生。
string queue_Name = channel.QueueDeclare().QueueName;
4、绑定(Bindings)
我们已经创建了一个转发器和队列,我们现在需要通过binding告诉转发器把消息发送给我们的队列。
channel.QueueBind(Queue_Name,Exchange_Name,””)参数1:队列名称 ;参数2:转发器名称。
二:源码实现
1、fanout类型
Producer源码:
static void Main(string[] args)
{
//创建目标,并将目标地址、用户名、密码记录
var factory = new ConnectionFactory();
factory.HostName = "xxx.xxx.xxx.xxx";
factory.UserName = "xxx";
factory.Password = "xxx";
//建立实例连接
var connection = factory.CreateConnection();
//建立连接RabbitMQ通道
var channel = connection.CreateModel();
//声明一个fanout类型的转发器Exchange_send
channel.ExchangeDeclare("Exchange_send","fanout");
//声明要发送的数据
byte[] body = Encoding.UTF8.GetBytes("It's a fanout type!");
channel.BasicPublish("Exchange_send", "",null,body);
Console.WriteLine(DateTime.Now.ToString()+":Fanout type send success");
Console.ReadKey();
}
Consumer源码:
static void Main(string[] args)
{
//创建目标,并将目标地址、用户名、密码记录
var factory = new ConnectionFactory();
factory.HostName = "xxx.xxx.xxx.xxx";
factory.UserName = "xxx";
factory.Password = "xxx";
//建立实例连接
var connection = factory.CreateConnection();
//建立连接RabbitMQ通道
var channel = connection.CreateModel();
//声明一个fanout类型的转发器Exchange_send
channel.ExchangeDeclare("Exchange_send", "fanout");
//创建一个非持久的、唯一的且自动删除的队列
string queue_Name = channel.QueueDeclare().QueueName;
//将队列绑定到转发器
channel.QueueBind(queue_Name, "Exchange_send", "");
//用于接收消息
QueueingBasicConsumer consumer = new QueueingBasicConsumer(channel);
//指定接收者,第二个参数false表示需要回执给rabbitmq,否则rabbitmq一直保存
channel.BasicConsume(queue_Name, false, consumer);
while(true)
{
//接收消息并处理
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(DateTime.Now.ToString() + ":Received {0}", message);
//消息处理完成后告诉客户端我已收到消息
var body1 = Encoding.UTF8.GetBytes("I have received the message");
channel.BasicPublish("", "receive", null, body1);
Console.WriteLine(DateTime.Now.ToString() + " send:{0}", "I have received the message");
//处理完了回执给rabbitmq,如果没有这行代码,RabbitMQ会一直存储这条消息
channel.BasicAck(ea.DeliveryTag, true);
}
Console.ReadKey();
}
2、direct类型
Producer源码:
private static String EXCHANGE_NAME = "Exchange_direct";
private static String[] LOGTYPE = { "warning", "error" };
static void Main(string[] args)
{
//创建目标,并将目标地址、用户名、密码记录
var factory = new ConnectionFactory();
factory.HostName = "xxx.xxx.xxx.xxx";
factory.UserName = "xxx";
factory.Password = "xxx";
//建立实例连接
var connection = factory.CreateConnection();
//建立连接RabbitMQ通道
var channel = connection.CreateModel();
//声明一个direct类型的转发器
channel.ExchangeDeclare(EXCHANGE_NAME, "direct");
// 发送5条消息
for (int i = 0; i < 5; i++)
{
String message = "";
if (i / 2 == 0)
{
message = LOGTYPE[0] + "_log :it's warning " + i;
// 发布消息至转发器,指定routingkey
channel.BasicPublish(EXCHANGE_NAME, LOGTYPE[0], null, Encoding.UTF8.GetBytes(message));
}
else
{
message = LOGTYPE[1] + "_log :it's error " + i;
// 发布消息至转发器,指定routingkey
channel.BasicPublish(EXCHANGE_NAME, LOGTYPE[1], null, Encoding.UTF8.GetBytes(message));
}
Console.WriteLine("I have sent '" + message + "'");
}
Console.ReadKey();
}
Consumer01源码
private static String EXCHANGE_NAME = "Exchange_direct";
private static String[] LOGTYPE = { "warning", "error" };
static void Main(string[] args)
{
//创建目标,并将目标地址、用户名、密码记录
var factory = new ConnectionFactory();
factory.HostName = "xxx.xxx.xxx.xxx";
factory.UserName = "xxx";
factory.Password = "xxx";
//建立实例连接
var connection = factory.CreateConnection();
//建立连接RabbitMQ通道
var channel = connection.CreateModel();
//声明一个fanout类型的转发器Exchange_send
channel.ExchangeDeclare(EXCHANGE_NAME, "direct");
//创建一个非持久的、唯一的且自动删除的队列
string queue_Name = channel.QueueDeclare().QueueName;
//将队列绑定到转发器
channel.QueueBind(queue_Name, EXCHANGE_NAME, LOGTYPE[0]);
//用于接收消息
QueueingBasicConsumer consumer = new QueueingBasicConsumer(channel);
//指定接收者,第二个参数false表示需要回执给rabbitmq,否则rabbitmq一直保存
channel.BasicConsume(queue_Name, false, consumer);
while(true)
{
//接收消息并处理
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
var body = ea.Body; var message = Encoding.UTF8.GetString(body);
Console.WriteLine(DateTime.Now.ToString() + ":Received {0}", message);
//消息处理完成后告诉客户端我已收到消息
var body1 = Encoding.UTF8.GetBytes("I have received the message");
channel.BasicPublish("", "receive", null, body1);
Console.WriteLine(DateTime.Now.ToString() + " send:{0}", "I have received the message");
//处理完了回执给rabbitmq,如果没有这行代码,RabbitMQ会一直存储这条消息
channel.BasicAck(ea.DeliveryTag, true);
}
Console.ReadKey();
}
Consumer02源码:
private static String EXCHANGE_NAME = "Exchange_direct";
private static String[] LOGTYPE = { "warning", "error" };
static void Main(string[] args)
{
//创建目标,并将目标地址、用户名、密码记录
var factory = new ConnectionFactory();
factory.HostName = "xxx.xxx.xxx.xxx";
factory.UserName = "xxx";
factory.Password = "xxx";
//建立实例连接
var connection = factory.CreateConnection();
//建立连接RabbitMQ通道
var channel = connection.CreateModel();
//声明一个fanout类型的转发器Exchange_send
channel.ExchangeDeclare(EXCHANGE_NAME, "direct");
//创建一个非持久的、唯一的且自动删除的队列
string queue_Name = channel.QueueDeclare().QueueName;
//将队列绑定到转发器
channel.QueueBind(queue_Name, EXCHANGE_NAME, LOGTYPE[1]);
//用于接收消息
QueueingBasicConsumer consumer = new QueueingBasicConsumer(channel);
//指定接收者,第二个参数false表示需要回执给rabbitmq,否则rabbitmq一直保存
channel.BasicConsume(queue_Name, false, consumer);
while(true)
{
//接收消息并处理
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
var body = ea.Body; var message = Encoding.UTF8.GetString(body);
Console.WriteLine(DateTime.Now.ToString() + ":Received {0}", message);
//消息处理完成后告诉客户端我已收到消息
var body1 = Encoding.UTF8.GetBytes("I have received the message");
channel.BasicPublish("", "receive", null, body1);
Console.WriteLine(DateTime.Now.ToString() + " send:{0}", "I have received the message");
//处理完了回执给rabbitmq,如果没有这行代码,RabbitMQ会一直存储这条消息
channel.BasicAck(ea.DeliveryTag, true);
}
Console.ReadKey();
}
3、topic类型
Producer源码:
private static String EXCHANGE_NAME = "Exchange_topic";
static void Main(string[] args)
{
//创建目标,并将目标地址、用户名、密码记录
var factory = new ConnectionFactory();
factory.HostName = "xxx.xxx.xxx.xxx";
factory.UserName = "xxx";
factory.Password = "xxx";
//建立实例连接
var connection = factory.CreateConnection();
//建立连接RabbitMQ通道
var channel = connection.CreateModel();
//声明一个direct类型的转发器
channel.ExchangeDeclare(EXCHANGE_NAME, "topic");
String[] LOGTYPE = new String[] { "kernal.info", "cron.warning", "auth.info", "kernel.critical" };
// 发送5条消息
for (int i = 0; i < 4; i++)
{
string message = LOGTYPE[i] + ":it's " + i;
// 发布消息至转发器,指定routingkey
channel.BasicPublish(EXCHANGE_NAME, LOGTYPE[i], null, Encoding.UTF8.GetBytes(message));
Console.WriteLine("I have sent '" + message + "'");
}
Console.ReadKey();
}
Consumer源码:
private static String EXCHANGE_NAME = "Exchange_topic";
static void Main(string[] args)
{
//创建目标,并将目标地址、用户名、密码记录
var factory = new ConnectionFactory();
factory.HostName = "xxx.xxx.xxx.xxx";
factory.UserName = "xxx";
factory.Password = "xxx";
//建立实例连接
var connection = factory.CreateConnection();
//建立连接RabbitMQ通道
var channel = connection.CreateModel();
//声明一个fanout类型的转发器Exchange_send
channel.ExchangeDeclare(EXCHANGE_NAME, "topic");
//创建一个非持久的、唯一的且自动删除的队列
string queue_Name = channel.QueueDeclare().QueueName;
//将队列绑定到转发器
channel.QueueBind(queue_Name, EXCHANGE_NAME, "cron.*");
//用于接收消息
QueueingBasicConsumer consumer = new QueueingBasicConsumer(channel);
//指定接收者,第二个参数false表示需要回执给rabbitmq,否则rabbitmq一直保存
channel.BasicConsume(queue_Name, false, consumer);
while(true)
{
//接收消息并处理
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Console.WriteLine(DateTime.Now.ToString() + ":Received {0}", message);
//消息处理完成后告诉客户端我已收到消息
var body1 = Encoding.UTF8.GetBytes("I have received the message");
channel.BasicPublish("", "receive", null, body1);
Console.WriteLine(DateTime.Now.ToString() + " send:{0}", "I have received the message");
//处理完了回执给rabbitmq,如果没有这行代码,RabbitMQ会一直存储这条消息
channel.BasicAck(ea.DeliveryTag, true);
}
Console.ReadKey();
}
不同Consumer接收不同消息,只需要改QueueBind的第三个参数即可。