RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递传递到了哪些队列中。
相反,生产者只能将消息发送到交换机(exchange),交换机工作的内容非常简单,一方面它接收来自生产者的消息,另一方面将它们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消息放到特定队列还是说把他们到许多队列中还是说应该丢弃它们。这就的由交换机的类型来决定。
直接(direct), 主题(topic) ,标题(headers) , 扇出(fanout)
在之前的学习中,我们使用的都是默认的交换机,空字符串指名的就是默认的交换机,消息发送是通过router-key
找到指定的队列
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
有时候,当我们连接rabbitmq时,我们期望需要一个全新的队列,用完即删,因此队列名字此时并不重要。
Channel channel = RabbitMQUtils.getChannel();
// 声明并创建 随机名字的队列 ,队列用完即删 (用完指的是断开连接)
channel.queueDeclare().getQueue();
什么是 bingding 呢,binding 其实是 exchange 和 queue 之间的桥梁,它告诉我们 exchange 和那个队列进行了绑定关系。比如说下面这张图告诉我们的就是 exchange 与 Q1 和 Q2 进行了绑定
Fanout 这种类型非常简单。正如从名称中猜到的那样,它是将接收到的所有消息广播到它知道的所有队列中。系统中默认有些 exchange 类型
测试
1、一个日志发出者,两个日志消费者
日志生产者
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtils.getChannel();
/**
* 声明创建一个交换机,类型是 fanout
* 即该交换机中的信息 是广播出来的
*/
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String message = scanner.nextLine();
// 将数据发送给交换机,
channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes("UTF-8"));
}
}
日志消费者1(消息打印)
public static void main(String[] args) throws Exception {
Channel channel = RabbitMQUtils.getChannel();
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
/**
* 生成一个临时队列,名称随机
* 当消费者断开与该队列的连接时,队列自动删除
*/
String queueName = channel.queueDeclare().getQueue();
/**
* 将临时队列,与指定exchange绑定,routerKey 也称之为 bindingKey
*/
channel.queueBind(queueName,EXCHANGE_NAME,"");
System.out.println("等待接收消息,并把消息打印。。。。");
DeliverCallback ackCallback = (consumerTag,delivery)->{
System.out.println("正在消费消息:"+ new String(delivery.getBody()));
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
CancelCallback cancelCallback = (consumerTag)->{
System.out.println("消息取消");
};
channel.basicConsume(queueName,false,ackCallback,cancelCallback);
}
日志消费者2(消息写入文件)
public static void main(String[] args) throws Exception{
String filePath = System.getProperty("user.dir")+ File.separator + "a.txt";
Channel channel = RabbitMQUtils.getChannel();
// 声明交换机 , 没有则创建
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
// 声明随机队列,没有则创建,用完即删
String queueName = channel.queueDeclare().getQueue();
// 将队列与交换机绑定
channel.queueBind(queueName,EXCHANGE_NAME,"");
System.out.println("等待接收消息,并把消息写入文件。。。。");
// 接收到消息的回调
DeliverCallback ackCallback = (consumerTag,delivery)->{
String message = new String(delivery.getBody());
System.out.println("接收到消息:"+message+" 正准备写入文件");
System.out.println("文件路径:"+filePath);
FileUtils.writeStringToFile(new File(filePath),message,"UTF-8");
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
//从channel中消费消息
channel.basicConsume(queueName,false,ackCallback,(consumerTag -> {}));
}
启动后发现,出现了两个随机生成的队列,与logs交换机绑定。每一个队列归不同的消费者所有,在消费者处创建。由于交换机是fanout(广播类型,实际上队列都将收到消息,消费者会从对应的队列中取消息)
执行结果
什么是 bindings,绑定是交换机和队列之间的桥梁关系。也可以这么理解: 队列只对它绑定的交换机的消息感兴趣。绑定用参数:routingKey 来表示也可称该参数为 binding key, 创建绑定我们用代码:channel.queueBind(queueName, EXCHANGE_NAME, “routingKey”);绑定之后的意义由其交换类型决定。
routerKey的意义在于: 当生产者发消息给对应交换机,指定routerKey。交换机会通过routerKey(bindingKey)找对应的队列将消息发送过去
1、 Direct exchange 介绍
上面示例中,我们的日志系统将所有消息广播给所有消费者,对此我们想做一些改变。
例如我们希望将日志消息写入磁盘的程序仅接收严重错误(errros),而不存储哪些警告(warning)或信息(info)日志消息避免浪费磁盘空间。
Fanout 这种交换类型并不能给我们带来很大的灵活性-它只能进行无意识的广播,在这里我们将使用 direct 这种类型来进行替换,这种类型的工作方式是,消息只去到它绑定的routingKey 队列中去。
2、单重绑定
在上面这张图中,我们可以看到 X 绑定了两个队列,绑定类型是 direct。队列 Q1 绑定键为 orange, 队列 Q2 绑定键有两个:一个绑定键为 black,另一个绑定键为 green.
在这种绑定情况下,生产者发布消息到 exchange 上,绑定键为 orange 的消息会被发布到队列
Q1。绑定键为 blackgreen 和的消息会被发布到队列 Q2,其他消息类型的消息将被丢弃。
当然如果 exchange 的绑定类型是direct,但是它绑定的多个队列的 key 如果都相同,在这种情况下虽然绑定类型是 direct 但是它表现的就和 fanout 有点类似了,就跟广播差不多,如上图所示。
测试
一个日志生产者,两个日志处理者
1、日志生产者
public static void main(String[] args) throws Exception{
Channel channel = RabbitMQUtils.getChannel();
// 声明交换机,没有则创建,类型是direct
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//创建多个 bindingKey
Map<String, String> bindingKeyMap = new HashMap<>();
bindingKeyMap.put("info"," 普 通 info 信 息 ");
bindingKeyMap.put("warning"," 警 告 warning 信 息 ");
bindingKeyMap.put("error","错误 error 信息");
//debug 没有消费这接收这个消息 所有就丢失了
bindingKeyMap.put("debug","调试 debug 信息");
for (String bindingKey : bindingKeyMap.keySet()) {
String value = bindingKeyMap.get(bindingKey);
channel.basicPublish(EXCHANGE_NAME,bindingKey,null,value.getBytes("UTF-8"));
System.out.println("log生产者,发送消息:"+ value);
}
}
2、日志处理者(消息打印)
public static void main(String[] args) throws Exception{
Channel channel = RabbitMQUtils.getChannel();
// 声明交换机,不存在则创建
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String queueName = channel.queueDeclare().getQueue();
// 将队列和交换机之间建立联系,绑定字符串为 error
channel.queueBind(queueName,EXCHANGE_NAME,"error");
DeliverCallback deliverCallback = (consumerTag,delivery)->{
System.out.println("正在将消息写入文件");
String message = new String(delivery.getBody());
System.out.println("正在写入消息到文件:"+ message);
FileUtils.writeStringToFile(new File(System.getProperty("user.dir")+File.separator+"b.txt"),message,"UTF-8");
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
CancelCallback cancelCallback =(consumer)->{
System.out.println("消息消费取消");
};
System.out.println("准备读取数据到文件中。。。");
channel.basicConsume(queueName,false,deliverCallback,cancelCallback);
}
3、日志处理者,写入文件
public static void main(String[] args) throws Exception{
Channel channel = RabbitMQUtils.getChannel();
// 声明交换机,不存在则创建
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String queueName = channel.queueDeclare().getQueue();
// 将队列和交换机之间建立联系,绑定字符串为 info,warning
channel.queueBind(queueName,EXCHANGE_NAME,"info");
channel.queueBind(queueName,EXCHANGE_NAME,"warning");
DeliverCallback deliverCallback = (consumerTag,delivery)->{
System.out.println(new String(delivery.getBody()));
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
};
CancelCallback cancelCallback =(consumer)->{
System.out.println("消息消费取消");
};
System.out.println("准备读取数据并打印。。。");
channel.basicConsume(queueName,false,deliverCallback,cancelCallback);
}
directLogs交换机 关联两个队列,绑定三个bindingKey
发送到类型是 topic 交换机的消息的 routing_key 不能随意写,必须满足一定的要求,它必须是一个单词列表,以点号分隔开。这些单词可以是任意单词,比如说:“stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabbit”.这种类型的。当然这个单词列表最多不能超过 255 个字节。
在这个规则列表中,其中有两个替换符是大家需要注意的
*(星号)可以代替一个单词
#(井号)可以替代零个或多个单词
测试
下图绑定关系如下
Q1–>绑定的是
中间带 orange 带 3 个单词的字符串(.orange.)
Q2–>绑定的是
最后一个单词是 rabbit 的 3 个单词(..rabbit) 第一个单词是 lazy 的多个单词(lazy.#)
上图是一个队列绑定关系图,我们来看看他们之间数据接收情况是怎么样的
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
个人博客:https://www.xiaoxuya.top/