RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递传递到了哪些队列中。
相反,生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将它们推入队列
总共有以下类型:
直接(direct), 主题(topic) ,标题(headers) , 扇出(fanout)
在本教程的前面部分我们对 exchange 一无所知,但仍然能够将消息发送到队列。之前能实现的
原因是因为我们使用的是默认交换,我们通过空字符串(“”)进行标识。
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
之前的章节我们使用的是具有特定名称的队列(还记得 hello 和 ack_queue 吗?)。队列的名称我们来说至关重要-我们需要指定我们的消费者去消费哪个队列的消息。
每当我们连接到 Rabbit 时,我们都需要一个全新的空队列,为此我们可以创建一个具有随机名称的队列,或者能让服务器为我们选择一个随机队列名称那就更好了。其次一旦我们断开了消费者的连接,队列将被自动删除。
创建临时队列的方式如下:
String queue = channel.queueDeclare().getQueue();
创建出来之后长成这样:

绑定是exchange和queue之前的桥梁,它告诉我们 exchange 和那些队列进行了绑定关系。比如说下面这张图告诉我们的就是 X 与 Q1 和 Q2 进行了绑定

Fanout 这种类型非常简单。正如从名称中猜到的那样,它是将接收到的所有消息广播到它知道的所有队列中
rabbitmq中默认有fanout 的exchange 类型:

构建一个简单的日志系统。它将由两个程序组成:第一个程序将发出日志消息,第二个程序是消费者。其中我们会启动两个消费者,其中一个消费者接收到消息后把日志存储在磁盘,另外一个消费者接收到消息后把消息打印在屏幕上,事实上第一个程序发出的日志消息将广播给所有消费者

交换机logs和临时队列的绑定关系如下:

ReceiveLogs01 将接收到的消息打印在控制台
public class ReceiveLogs01 {private static final String EXCHANGE_NAME = "logs";public static void main(String[] args) throws Exception {Channel channel = RabbitMqUtils.getChannel();// 1、生成一个fanout类型的交换机channel.exchangeDeclare(EXCHANGE_NAME, "fanout");/*** 2、生成一个临时队列,队列的名字是随机的* 当消费者断开和该队列的连接时,队列自动删除*/String queue = channel.queueDeclare().getQueue();//3、临时队列绑定我们的交换机,其中绑定关系为空串channel.queueBind(queue, EXCHANGE_NAME, "");System.out.println("等待接收消息,将接收到的消息打印在屏幕");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String messgae = new String(delivery.getBody(), "utf-8");System.out.println("控制台打印接收到的消息:" + messgae);};channel.basicConsume(queue, true, deliverCallback, consumerTag -> {});}
}
ReceiveLogs02 将接收到的消息模拟持久化到磁盘
public class ReceiveLogs02 {private static final String EXCHANGE_NAME = "logs";public static void main(String[] args) throws Exception {Channel channel = RabbitMqUtils.getChannel();// 1、生成一个fanout类型的交换机channel.exchangeDeclare(EXCHANGE_NAME, "fanout");/*** 2、生成一个临时队列,队列的名字是随机的* 当消费者断开和该队列的连接时,队列自动删除*/String queue = channel.queueDeclare().getQueue();//3、临时队列绑定我们的交换机,其中绑定关系为空串channel.queueBind(queue, EXCHANGE_NAME, "");System.out.println("等待接收消息,将接收到的消息持久化到磁盘");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String messgae = new String(delivery.getBody(), "utf-8");System.out.println("---- 模拟写入磁盘" + messgae);};channel.basicConsume(queue, true, deliverCallback, consumerTag -> {});}
}
生产者生产消息
public class ProduceLog {private static final String EXCHANGE_NAME = "logs";public static void main(String[] args) throws Exception {try (Channel channel = RabbitMqUtils.getChannel();) {channel.exchangeDeclare(EXCHANGE_NAME, "fanout");//从控制台当中接受信息Scanner scanner = new Scanner(System.in);while (scanner.hasNext()) {String message = scanner.next();channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());System.out.println("生产者发送消息:" + message);}}}}
结果: 生产者发送一条消息时,两个消费者都接收到了消息



绑定是交换机和队列之间的桥梁关系。也可以这么理解:队列只对它绑定的交换机的消息感兴趣
创建绑定我们用代码:channel.queueBind(queueName, EXCHANGE_NAME, “routingKey”);
上一节中的我们的日志系统将所有消息广播给所有消费者,对此我们想做一些改变,例如我们希望将日志消息写入磁盘的程序仅接收严重错误(errros),而不存储哪些警告(warning)或信息(info)日志消息避免浪费磁盘空间。
Fanout 这种交换类型并不能给我们带来很大的灵活性-它只能进行无意识的广播,在这里我们将使用 direct 这种类型来进行替换,这种类型的工作方式是,消息只去到它绑定的routingKey 队列中去



代码如下:
// 消费者C1
public class C1 {private static final String EXCHANGE_NAME = "X";private static final String QUERE_NAME = "console";public static void main(String[] args) throws Exception {Channel channel = RabbitMqUtils.getChannel();// 1、生成一个fanout类型的交换机channel.exchangeDeclare(EXCHANGE_NAME, "direct");// 2、申明一个队列channel.queueDeclare(QUERE_NAME, false, false, false, null);//3、队列绑定交换机channel.queueBind(QUERE_NAME, EXCHANGE_NAME, "info");channel.queueBind(QUERE_NAME, EXCHANGE_NAME, "warning");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String messgae = new String(delivery.getBody(), "utf-8");System.out.println("0.0----模拟控制台打印:" + messgae);};channel.basicConsume(QUERE_NAME, true, deliverCallback, consumerTag -> {});}
}// 消费者C2
public class C2 {private static final String EXCHANGE_NAME = "X";private static final String QUERE_NAME = "disk";public static void main(String[] args) throws Exception {Channel channel = RabbitMqUtils.getChannel();// 1、生成一个fanout类型的交换机channel.exchangeDeclare(EXCHANGE_NAME, "direct");channel.queueDeclare(QUERE_NAME,false,false,false,null);//3、临时队列绑定我们的交换机,其中绑定关系为空串channel.queueBind(QUERE_NAME, EXCHANGE_NAME, "error");DeliverCallback deliverCallback = (consumerTag, delivery) -> {String messgae = new String(delivery.getBody(), "utf-8");System.out.println("---- 模拟写入磁盘" + messgae);};channel.basicConsume(QUERE_NAME, true, deliverCallback, consumerTag -> {});}
}// 消息生产者
public class ProduceLog {private static final String EXCHANGE_NAME = "X";public static void main(String[] args) throws Exception {try (Channel channel = RabbitMqUtils.getChannel();) {channel.exchangeDeclare(EXCHANGE_NAME, "direct");//创建多个 bindingKeyMap bindingKeyMap = new HashMap<>();bindingKeyMap.put("info","普通 info 信息");bindingKeyMap.put("warning","警告 warning 信息");bindingKeyMap.put("error","错误 error 信息");//debug 没有消费这接收这个消息 所有就丢失了bindingKeyMap.put("debug","调试 debug 信息");for (Map.Entry bindingKeyEntry: bindingKeyMap.entrySet()){String bindingKey = bindingKeyEntry.getKey();String message = bindingKeyEntry.getValue();channel.basicPublish(EXCHANGE_NAME,bindingKey, null,message.getBytes("UTF-8"));System.out.println("生产者发出消息:" + message);}}}}
结果:可以看到 C1消费到了 warning和 info消息,C2消费到了error消息,debug消息由于没有匹配到任何路由键,所以没有到达任何队列,所以没有任何消费者进行处理



发送到 topic 交换机的消息的 routing_key 是有一定要求的,它必须是一个单词列表,以点号分隔开。
这些单词可以是任意单词,比如说:“stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”.
*在这个规则列表中,其中有两个替换符是大家需要注意的
(星号)可以代替一个单词
#(井号)可以替代零个或多个单词

上图是一个队列绑定关系图,我们来看看他们之间数据接收情况是怎么样的
quick.orange.rabbit 被队列 Q1Q2 接收到
lazy.orange.elephant 被队列 Q1Q2 接收到
quick.orange.fox 被队列 Q1 接收到
lazy.brown.fox 被队列 Q2 接收到
lazy.pink.rabbit 虽然满足两个绑定但只被队列 Q2 接收一次
quick.brown.fox 不匹配任何绑定不会被任何队列接收到会被丢弃
quick.orange.male.rabbit 是四个单词不匹配任何绑定会被丢弃
lazy.orange.male.rabbit 是四个单词但匹配 Q2