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

浙公网安备 33010602011771号