RabbitMQ(四):RabbitMQ--Exchange

Exchange

在前面文章中介绍了RabbitMQ传递消息的过程:

  • 生产者是发送消息的应用程序
  • 队列是存储消息的缓冲区
  • 消费者是接收消息的用户应用程序

但是,RabbitMQ消息模型的核心思想是,生产者不会直接向队列发送任何消息。实际上,很多时候生产者甚至不知道消息是否将被传递到任何队列。生产者只能将消息发送到交换器(Exchange)。

RabbitMQ提供了Exchange,它类似于路由器的功能,将消息路由到一个或者多个队列上。Exchange一方面从生产者接收消息,另一方面将消息推送到队列。但exchange必须知道如何处理接收到的消息,比如应该将消息传递到某一个队列还是传递到多个队列。这些规则由exchange type定义。

 

RabbitMQ常用的交换器有四种:

  • headers:不依赖路由规则来路由消息,而是根据消息内容的headers属性进行匹配。实际不常用
  • direct:消费者绑定的队列名必须和发布者指定的路由名称一致
  • fanout:消息广播,将消息分发到exchange绑定的所有队列上
  • topic:模式匹配的路由规则

 

fanout

fanout是广播模式,将队列(queue)与exchange进行绑定,消息从发送端发出后,RabbitMQ就会分发给所有与该exchange绑定的队列中。路由机制如下图:

 

修改Send.cs代码:

1     //使用fanout类型 指定exchange名称
2     channel.ExchangeDeclare(exchange: "logs", type: ExchangeType.Fanout);
3                     
4     string message = GetMessage(args);
5                     
6     var body = Encoding.UTF8.GetBytes(message);
7                     
8     //发布到指定的exchange,不指定routingkey
9     channel.BasicPublish(exchange: "logs", routingKey: "", basicProperties: null, body: body);

修改Receive.cs代码:

 1     //申明fanout类型exchange
 2     channel.ExchangeDeclare(exchange: "logs", type: ExchangeType.Fanout);
 3 
 4     //创建一个非持久、独占的、随机生成名称的自动删除队列
 5     var queueName = channel.QueueDeclare().QueueName;
 6 
 7     //绑定队列到指定的exchange,不指定routingkey
 8     channel.QueueBind(queue: queueName, exchange: "logs", routingKey: "");
 9                     
10     var consumer = new EventingBasicConsumer(channel);
11 
12     consumer.Received += (model, ea) =>
13     {
14         var body = ea.Body;
15         var message = Encoding.UTF8.GetString(body.ToArray());
16         Console.WriteLine("Received: {0}", message);
17     };
18     channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);

通过CMD打开Send.exe发送消息,两个接收端同时收到相同的消息

 

direct

direct属于完全匹配、单播的模式,是根据路由名称与routingkey来寻找队列。路由机制如下图:

 

如果这样设置,使用routingkey为orange发布到exchange的消息将被路由到队列Q1,使用routingkey为black/green发布到exchange的消息将被路由到队列Q1.其他的消息将会被丢弃。

 修改Send.cs代码:

 1     //使用direct类型 指定exchange名称
 2     channel.ExchangeDeclare(exchange: "direct_logs", type: "direct");
 3 
 4     var key = (args.Length > 0) ? args[0] : "info";
 5 
 6     string message = (args.Length > 1)
 7                     ? string.Join(" ", args.Skip(1).ToArray())
 8                     : "hello direct exchange";
 9 
10     var body = Encoding.UTF8.GetBytes(message);
11 
12     //发布到指定的exchange,必须指定routingkey
13     channel.BasicPublish(exchange: "direct_logs", routingKey: key, basicProperties: null, body: body);

修改Receive.cs代码:

 1     //申明direct类型exchange
 2     channel.ExchangeDeclare(exchange: "direct_logs", type: "direct");
 3 
 4     //创建一个非持久、独占的、随机生成名称的自动删除队列
 5     var queueName = channel.QueueDeclare().QueueName;
 6 
 7     if(args.Length < 1)
 8     {
 9         Environment.ExitCode = 1;
10         return;
11     }
12 
13     foreach(var key in args)
14     {
15         //绑定队列到指定的exchange,需指定routingkey
16         channel.QueueBind(queue: queueName, exchange: "direct_logs", routingKey: key);
17     }
18 
19     var consumer = new EventingBasicConsumer(channel);
20 
21     consumer.Received += (model, ea) =>
22     {
23         var body = ea.Body;
24         var message = Encoding.UTF8.GetString(body.ToArray());
25         Console.WriteLine("Received: {0}", message);
26     };
27     channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);

运行结果:

direct-multiple bindings

如果使用同一routingkey绑定多个队列也是可以的。这种情况下消息的分发行为将类似于fanout。路由机制如下图:

 

topic

topic模式可以看作是direct的升级,它根据routingkey的设置来进行匹配。支持两种通配符:

*:代表任意的一个词。例如:topic.rabbitmq.*这个routingkey,可以匹配到topic.rabbitmq.one,topic.rabbitmq.two,topic.rabbitmq.abc

#:代表任意零个或多个词。例如:topic.#这个routingkey,可以匹配到topic.rabbitmq.one,topic.rabbitmq.two,topic.topic.abc

ps:单词之间用"."分割。

示例:

在此示例中,发送端将发送关于描述动物的消息。消息将由三个单词组成的routingkey发送:<敏捷度><颜色><物种>

创建对应的队列

Q1:接收“橙色动物”的消息

Q2:接收“兔子,以及敏捷度低的动物”的消息

Send.cs

 1     //使用topic类型 指定exchange名称
 2     channel.ExchangeDeclare(exchange: "topic_logs", type: "topic");
 3 
 4     var key = (args.Length > 0) ? args[0] : "anontmous.info";
 5 
 6     string message = (args.Length > 0) 
 7         ? string.Join("",args.Skip(1).ToArray()) 
 8         : "hello topic exchange";
 9 
10     var body = Encoding.UTF8.GetBytes(message);
11 
12     //发布到指定的exchange,指定routingkey
13     channel.BasicPublish(exchange: "topic_logs", routingKey: key, basicProperties: null, body: body);
14 
15     Console.WriteLine("Sent '{0}':'{1}'", key, message);

Receive.cs

 1     //申明topic类型exchange
 2     channel.ExchangeDeclare(exchange: "topic_logs", type: "topic");
 3 
 4     var queueName = channel.QueueDeclare().QueueName;
 5 
 6     if (args.Length < 1)
 7     {
 8         Environment.ExitCode = 1;
 9         return;
10     }
11 
12     foreach (var key in args)
13     {
14         //绑定队列到指定的exchange,指定routingkey
15         channel.QueueBind(queue: queueName, exchange: "topic_logs", routingKey: key);
16     }
17 
18     var consumer = new EventingBasicConsumer(channel);
19 
20     consumer.Received += (model, ea) =>
21     {
22         var body = ea.Body;
23         var message = Encoding.UTF8.GetString(body.ToArray());
24         Console.WriteLine("Received: {0}", message);
25     };
26     channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);

运行结果:

由结果可以看出

  • routingkey为[快速的.橙色的.兔子]将传送到两个队列中
  • routingkey为[缓慢的.橙色的.大象]也传送到两个队列中
  • routingkey为[快速的.橙色的.狐狸]只会传到第一个队列
  • routingkey为[缓慢的.棕色的.狐狸]只会传到第二个队列
  • routingkey为[缓慢的.粉色的.兔子]将会传到第二个队列
  • routingkey为[快速的.棕色的.狐狸]没有对应的匹配,将会丢失
  • routingkey为[缓慢的.橙色的.公的.兔子]将会传到第二个队列(即使有四个单词)

topic类型的exchange可以像其他类型的一样工作:

当队列的routingkey设置为“#”时,将会接收所有的消息,类似于fanout;

当队列的routingkey没有使用通配符“#”和“*”时,topic类型就像direct一样工作。

 

参考:

https://www.rabbitmq.com/getstarted.html

 

posted @ 2020-10-29 14:03  凌晨4time  阅读(265)  评论(0)    收藏  举报