在前面的工作队列中,每个任务都只会传递给一个消费者。
而在发布/订阅模式下,我们会向多个消费者传递信息。
在这种模式下,发布的消息将被广播给所有的消费者。
RabbitMQ中的消息传递模型的核心思想是生产者永远不会将任何信息直接发送到队列中,实际上,生产者甚至不知道消息是否会被传送到队列中。
相反,生产者只能把信息发送到exchange(交换中心)中,exchange也非常简单,一方面它接收来自生产者的消息,另一方面把他们推送到队列中。exchange必须知道如何处理它接收到的消息。(说白了就是exchange必须知道把接收到的消息是附加到特定队列、还是附加到所有队列,亦或者是丢掉这些接收到的消息。这些规则应该由exchange的类型来定义。下面是exchange的4种类型的详细介绍)
exchange有4中类型:
扇形交换中心是最基本的exchange类型,它做的事情非常简单:(就是广播消息)。扇形交换中心会把能接收到的消息全部发送给绑定在自己身上的队列。因为只是简单的广播消息,所以扇形交换中心处理消息的速度也是所有exchange类型里面最快的。
直接交换中心是一个带路由功能的exchange,一个队列会和一个exchange绑定,除此之外再绑定一个routing_Key
,当消息被发送的时候,需要指定一个routing_Key
,这个消息被送到exchange的时候,就会被exchange送到指定的队列里面去。同样的一个routing_Key
也是支持应用到多个队列中的。
这样,当一个交换机绑定多个队列,就会被送到对应的队列去处理。
适用场景:有优先级的任务,根据任务的优先级把消息发送到对应的队列,这样可以指派更多的资源去处理高优先级的队列。
直连交换中心的routing_key
方案非常简单,如果我们希望一条消息发送给多个队列,那么这个exchange需要绑定非常多的routing_key
,假设如果每个exchange上都绑定非常一堆的routing_key
连接到各个队列中,那么消息的管理将会非常困难。
所以RabbitMQ提供了一种主题交换中心(topic exchange),发送到主题交换中心上的消息需要携带指定规则的routing_key
,主题交换中心会根据这个规则将数据发送到对应的多个队列上。
主题交换机的routing_key
需要有一定的规则,交换机和队列的routing_key
需要采用*.#.*.....
的格式,每个部分用.
分开,其中:
*
表示一个单词。#
表示任意数量(零个或多个)单词。
假设有一条消息的routing_key
为fast.rabbit.white
,那么带有这样的routing_key
的几个队列都会接收这条消息:
① fast..
② ..white
③ fast.#
④ ……
下图详细展示了主题交换中心的工作模式:
当一个队列的绑定键为#的时候,这个队列将会无视消息的路由键,接收所有的消息。
首部交换中心是忽略routing_key
的一种路由方式。路由器和交换中心的路由规则是通过Headers
信息来交换的,这个有点像HTTP
的Headers
。将一个exchange声明为首部exchange,绑定一个队列的时候,定义一个Hash
的数据结构,消息发送的时候,会携带一组hash数据结构的信息,当Hash
的内容匹配上的时候,消息就会被写入队列。
绑定exchange和队列的时候,Hash结构中要求携带一个键“x-match”,这个键的值可以是any
或者是all
,这代表消息携带的Hash
是需要全部匹配(all),还是仅匹配一个键(any)就可以了。相比直连交换中心,首部交换中心的优势是匹配的规则不被限定为字符串(String)。
在前面的模式中,我们使用的是指定名称的队列,列出队列至关重要,我们需要将消费者指向同一个队列,当我们向在生产者和消费者之间分享队列时,给队列一个名字是很重要的。
也就是说,首先我们需要创建exchange,然后创建一个临时性的队列,然后把队列和exchange绑定在一起。
生产者发布消息和之前没有多少区别,目前的变化是我们想发布我们的消息到exchange中,发送时提供一个routing_key
,但是扇形exchange将忽略routing_key
。
下面给出源代码:
其中的pom依赖在https://blog.csdn.net/m0_37884977/article/details/80348932 已经有了。
发布消息的生产者:
package cn.lframe.amqp.rabbitmq.publish_subscribe_mode;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @author Lframe
* @create2018 -05 -17 -16:17
*/
public class EmitLog {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 先声明exchange。
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
String message = getMessage(argv);
// 生产者发布一个消息,参数依次是exchange的名称,routingKey,props。
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
private static String getMessage(String[] strings) {
if (strings.length < 1)
return "info: Hello World!";
return joinStrings(strings, " ");
}
private static String joinStrings(String[] strings, String delimiter) {
int length = strings.length;
if (length == 0) return "";
StringBuilder words = new StringBuilder(strings[0]);
for (int i = 1; i < length; i++) {
words.append(delimiter).append(strings[i]);
}
return words.toString();
}
}
订阅消息的消费者
package cn.lframe.amqp.rabbitmq.publish_subscribe_mode;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* @author Lframe
* @create2018 -05 -17 -16:17
*/
public class EmitLog {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 先声明exchange。
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
String message = getMessage(argv);
// 生产者发布一个消息,参数依次是exchange的名称,routingKey,props。
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "'");
channel.close();
connection.close();
}
private static String getMessage(String[] strings) {
if (strings.length < 1)
return "info: Hello World!";
return joinStrings(strings, " ");
}
private static String joinStrings(String[] strings, String delimiter) {
int length = strings.length;
if (length == 0) return "";
StringBuilder words = new StringBuilder(strings[0]);
for (int i = 1; i < length; i++) {
words.append(delimiter).append(strings[i]);
}
return words.toString();
}
}
RabbitMQ的发布订阅模式中,生产者生产的所有消息都会被复制一份发送到它的每个订阅者(这里我们的exchange是扇形交换中心(Fanout exchange))