RabbitMQ学习(七):交换器

news2024/9/28 3:25:28

〇、前言

在之前的内容中,我们创建了一个工作队列。我们假设的是工作队列背后,每个任务都恰好交付给一个消 费者(工作进程)。在今天的内容中,我们将做一些完全不同的事情——我们将消息传达给多个消费者。这种模式 称为 “发布/订阅”。

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

一、Exchanges 概念

RabbitMQ 消息传递模型的核心思想是: 生产者生产的消息从不会直接发送到队列。实际上,通常生产者甚至都不知道这些消息传递传递到了哪些队列中。相反,生产者只能将消息发送到交换机(exchange)。

交换机工作的内容非常简单,一方面它接收来 自生产者的消息,另一方面将它们推入队列。交换机必须确切知道如何处理收到的消息。是应该把这些消 息放到特定队列还是说把他们到许多队列中还是说应该丢弃它们。这就的由交换机的类型来决定。

1.1 Exchanges 的类型

总共有以下类型: 直接(direct), 主题(topic) ,标题(headers) , 扇出(fanout)

1.2 无名 exchange

在前面部分中我们对 exchange 没有任何操作,但仍然能够将消息发送到队列。 原因是我们使用的是默认交换,我们通过空字符串(“”)进行标识。

channel.basicPublish("", "hello", null, message.getBytes());

第一个参数是交换机的名称。空字符串表示默认或无名称交换机。消息能路由发送到队列中其实 是由routingKey(bindingkey)绑定 key 指定的,如果它存在的话。

1.3 绑定

什么是 bingding 呢,binding 其实是 exchange 和 queue 之间的桥梁,它告诉我们 exchange 和那个队 列进行了绑定关系。比如说下面这张图告诉我们的就是 X 与 Q1 和 Q2 进行了绑定 。

二、fanout类型

2.1 fanout概述

Fanout 这种类型非常简单。正如名称,它是将接收到的所有消息广播到它知道的所有队列中。系统中默认有些 exchange类型。

2.2 fanout实战

消费者1将接受到的消息打印在控制台,核心代码:

//绑定fanout
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//创建一个临时的队列 队列的名称是随机的,当消费者断开和该队列的连接时 队列自动删除
String queueName = channel.queueDeclare().getQueue();
//把该临时队列绑定我们的 exchange 其中 routingkey(也称之为 binding key)为空字符串
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println("等待接收消息,把接收到的消息打印在屏幕.....");

消费者2将接受到的消息保存在磁盘,核心代码:

//绑定fanout
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//生成一个临时的队列 队列的名称是随机的,当消费者断开和该队列的连接时 队列自动删除
String queueName = channel.queueDeclare().getQueue();
//把该临时队列绑定我们的 exchange 其中 routingkey(也称之为 binding key)为空字符串
channel.queueBind(queueName, EXCHANGE_NAME, "");

//写入磁盘
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
     String message = new String(delivery.getBody(), "UTF-8");
     File file = new File("C:\\work\\rabbitmq_info.txt");
     FileUtils.writeStringToFile(file,message,"UTF-8");
     System.out.println("数据写入文件成功");
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });

生产者核心代码:

/**
 * 声明一个 exchange
 * 1.exchange 的名称
 * 2.exchange 的类型
 */
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
//发消息
while (sc.hasNext()) {
     channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
     System.out.println("生产者发出消息" + message);
}

结果是两个消费者都能收到生产者发送的消息。

需要注意的是,交换机把消息分发给了两个队列,在每个队列中一个消息仍然只是被消费了一次

三、direct类型

3.1 direct概述

首先我们再次来回顾一下什么是 bindings,绑定是交换机和队列之间的桥梁关系。也可以这么理解:队列只对它绑定的交换机的消息感兴趣。绑定用参数:routingKey 来表示也可称该参数为 binding key,创建绑定我们用代码:channel.queueBind(queueName, EXCHANGE_NAME, "routingKey");绑定之后的 意义由其交换类型决定。

上一节中的我们的日志系统将所有消息广播给所有消费者,对此我们想做一些改变,例如我们希 望将日志消息写入磁盘的程序仅接收严重错误(errros),而不存储哪些警告(warning)或信息(info)日志 消息避免浪费磁盘空间。Fanout 这种交换类型并不能给我们带来很大的灵活性——它只能进行无意识的 广播。在这里我们将使用 direct 这种类型来进行替换,这种类型的工作方式是,消息只去到它绑定 routingKey 队列中去。

在上面这张图中,我们可以看到 X 绑定了两个队列,绑定类型是 direct。队列 Q1 绑定键为orange,队列 Q2 绑定键有两个:一个绑定键为 black,另一个绑定键为 green。

在这种绑定情况下,生产者发布消息到 exchange 上,绑定键为 orange 的消息会被发布到队列 Q1。绑定键为 blac和kgreen 的消息会被发布到队列 Q2,其他消息类型的消息将被丢弃。

当然如果 exchange 的绑定类型是 direct,但是它绑定的多个队列的 key 如果都相同,在这种情 况下虽然绑定类型是 direct 但是它表现的就和 fanout 有点类似了,就跟广播差不多,如上图所示。这也被称为多重绑定。

3.2 direct实战

消费者1(处理error)核心代码:

channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare("disk", false, false, false, null);
//绑定error
channel.queueBind(queueName, EXCHANGE_NAME, "error");

DeliverCallback deliverCallback = (consumerTag, delivery) -> {
     String message = new String(delivery.getBody(), "UTF-8");
     message="接收绑定键:"+delivery.getEnvelope().getRoutingKey()+",消息:"+message;
     FileUtils.writeStringToFile(new File("C:\\work\\rabbitmq_info.txt"), message, "UTF-8");
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});

消费者2(处理info和warning)核心代码:

channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
channel.queueDeclare("console", false, false, false, null);
//绑定info和warning
channel.queueBind(queueName, EXCHANGE_NAME, "info");
channel.queueBind(queueName, EXCHANGE_NAME, "warning");

DeliverCallback deliverCallback = (consumerTag, delivery) -> {
     String message = new String(delivery.getBody(), "UTF-8");
     System.out.println("绑定键:"+delivery.getEnvelope().getRoutingKey()+",消息:"+message);
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {})

生产者核心代码:

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 (Map.Entry<String, String> bindingKeyEntry: bindingKeyMap.entrySet()){
    String bindingKey = bindingKeyEntry.getKey();
    String message = bindingKeyEntry.getValue();
    channel.basicPublish(EXCHANGE_NAME,bindingKey, null,
    message.getBytes("UTF-8"));
}

四、topic类型

4.1 topic类型的作用

在上一个小节中,我们改进了日志记录系统。我们没有使用只能进行随意广播的 fanout 交换机,而是 使用了 direct 交换机,从而有能实现有选择性地接收日志。

尽管使用 direct 交换机改进了我们的系统,但是它仍然存在局限性——比方说我们想接收的日志类型有 info.base 和 info.advantage,某个队列只想 info.base 的消息,那这个时候 direct 就办不到了。这个时候 就只能使用 topic 类型。

4.2 topic类型的要求

发送到类型是 topic 交换机的消息的 routing_key 不能随意写,必须满足一定的要求,它必须是一个单 词列表,以点号分隔开。这些单词可以是任意单词,比如说:"stock.usd.nyse"、"nyse.vmw"、 "quick.orange.rabbit"这种类型的。当然这个单词列表最多不能超过 255 个字节。

在这个规则列表中,其中有两个替换符是大家需要注意的 :*(星号)可以代替一个单词 、#(井号)可以替代零个或多个单词。

4.3 topic匹配案例

下图绑定关系如下 :

Q1-->绑定的是 :中间带 orange 带 3 个单词的字符串(*.orange.*)

Q2-->绑定的是 :最后一个单词是 rabbit 的 3 个单词(*.*.rabbit) 、

第一个单词是 lazy 的多个单词(lazy.#)

匹配结果如下表:

quick.orange.rabbit

被队列 Q1、Q2 接收到

lazy.orange.elephant

被队列 Q1、Q2 接收到

quick.orange.fox

被队列 Q1 接收到

lazy.brown.fox

被队列 Q2 接收到

lazy.pink.rabbit

虽然满足两个绑定,但只被队列 Q2 接收一次

quick.brown.fox

不匹配任何绑定,被丢弃

quick.orange.male.rabbit

是四个单词,不匹配任何绑定,被丢弃

lazy.orange.male.rabbit

是四个单词,但匹配 Q2

4.4 topic类型与fanout、direct的关系

当队列绑定关系是下列这种情况时需要引起注意 :

  • 当一个队列绑定键是#,那么这个队列将接收所有数据,其实是fanout类型

  • 如果队列绑定键当中没有#和*出现,那么该队列绑定类型就是 direct 类型

4.5 topic实战

消费者1核心代码:

channel.exchangeDeclare(EXCHANGE_NAME, "topic");
//声明 Q1 队列与绑定关系
channel.queueDeclare("Q1", false, false, false, null);
channel.queueBind(queueName, EXCHANGE_NAME, "*.orange.*");

DeliverCallback deliverCallback = (consumerTag, delivery) -> {
    String message = new String(delivery.getBody(), "UTF-8");
    System.out.println("接收队列:"+queueName+" 绑定键:"+delivery.getEnvelope().getRoutingKey()+",消息:"+message);
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});

消费者2核心代码:

channel.exchangeDeclare(EXCHANGE_NAME, "topic");

//声明 Q2 队列与绑定关系
channel.queueDeclare("Q2", false, false, false, null);
channel.queueBind(queueName, EXCHANGE_NAME, "*.*.rabbit");
channel.queueBind(queueName, EXCHANGE_NAME, "lazy.#");

DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
    System.out.println("接收队列:"+queueName+" 绑定键:"+delivery.getEnvelope().getRoutingKey()+",消息:"+message);
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});

生产者核心代码:

//交换机
channel.exchangeDeclare(EXCHANGE_NAME, "topic");

// Q1-->绑定的是 中间带 orange 带 3 个单词的字符串(*.orange.*)
// Q2-->绑定的是 最后一个单词是 rabbit 的 3 个单词(*.*.rabbit)
//                 第一个单词是 lazy 的多个单词(lazy.#)
Map<String, String> bindingKeyMap = new HashMap<>();
bindingKeyMap.put("quick.orange.rabbit","被队列 Q1Q2 接收到");
bindingKeyMap.put("lazy.orange.elephant","被队列 Q1Q2 接收到");
bindingKeyMap.put("quick.orange.fox","被队列 Q1 接收到");
bindingKeyMap.put("lazy.brown.fox","被队列 Q2 接收到");
bindingKeyMap.put("lazy.pink.rabbit","虽然满足两个绑定但只被队列 Q2 接收一次");
bindingKeyMap.put("quick.brown.fox","不匹配任何绑定不会被任何队列接收到会被丢弃");
bindingKeyMap.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
bindingKeyMap.put("lazy.orange.male.rabbit","是四个单词但匹配 Q2");

//发消息
for (Map.Entry<String, String> bindingKeyEntry: bindingKeyMap.entrySet()){
    String bindingKey = bindingKeyEntry.getKey();
    String message = bindingKeyEntry.getValue();
    channel.basicPublish(EXCHANGE_NAME, bindingKey, null, message.getBytes("UTF-8"));
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/349629.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

横板格斗类游戏实战:核心玩法介绍(一)

第一章讲解了横板格斗类游戏框架主要涉及到的一些模块设计与技术原理&#xff0c;本章节开始讲解横板格斗类游戏的玩法&#xff0c;美术资源与游戏的数值策划。我们以主要的截图为示意图&#xff0c;来把整个横板格斗类游戏的核心玩法和要实现的功能大致列一遍。对啦&#xff0…

QGIS中进行批量坡向计算

QGIS中进行坡向计算1. 坡向计算中的Z因子&#xff08;垂直单位与水平单位的比值&#xff09;2. 坡向计算步骤坡度计算的姊妹篇–坡向计算来了 1. 坡向计算中的Z因子&#xff08;垂直单位与水平单位的比值&#xff09; z 因子是一个转换因子&#xff0c;当输入表面的垂直坐标&…

BFC到底是什么?如何理解

BFC到底是什么&#xff1f; BFC全称&#xff1a;Block Formatting Context&#xff0c; 名为“块级格式化上下文”。 W3C官方解释&#xff1a;BFC 它决定了元素如何对其内容进行定位&#xff0c;以及与其他元素的关系和相互作用&#xff0c;当涉及到可视化布局时&#xff0c;B…

IMU调试方案

1.IMU 型号 QMI8658C IMU英文数据手册 参照连线与数据手册使用 类似的惯导模块开发https://www.cnblogs.com/rockyching2009/p/15071669.html 微雪 https://www.waveshare.net/wiki/RP2040-LCD-1.28 micro python &#xff1a;https://docs.micropython.org/en/latest/esp32/t…

ArcGIS API for JavaScript 4.15系列(7)——Dojo中的Ajax请求操作

1、前言 作为重要的前后端交互技术&#xff0c;Ajax被广泛应用于Web项目中。无论是jQuery时代的$.ajax还是Vue时代下的axios&#xff0c;它们都对Ajax做了良好的封装处理。而Dojo也不例外&#xff0c;开发者使用dojo/request模块可以轻松实现Ajax相关操作&#xff0c;下面开始…

UNP 简介

目录 从一个简单的时间获取客户端开始 socket 指定服务器 IP 地址与端口 与服务器建立连接并读取数据 简单的时间获取服务端 Unix 标准 从一个简单的时间获取客户端开始 接下来&#xff0c;将从一个使用 TCP 连接的获取时间的客户端开始。 // 以下代码与 UNP intro/dayt…

后台管理项目重构为vue3.0

目录前言&#xff1a;为什么要重构项目&#xff1f;重构的目的具体案例下载项目一. 为什么要重构后台管理项目二. 安装项目所需的vue3.0 插件三. 具体代码重构四. 在更改中遇到的bug总结前言&#xff1a; 我们平常玩的游戏有时需要更新出新的内容&#xff0c;我们的项目也需要…

组件化、模块化、集中式、分布式、服务化、面向服务的架构、微服务架构

目录 1.组件化与模块化 1.1.组件化 2.模块化 2.1.模块化和组件化的区别 3.集中式与分布式 3.1.集中式 3.2.分布式 4.服务化 5.面向服务的架构 5.1.什么是SOA 5.2.实现SOA 5.3.面向对象和面向服务的对比 6.微服务架构 6.1.SOA和微服务 7.总结 最近最火的词是什么…

1月份 GameFi 行业报告

Jan. 2023&#xff0c; DanielData Source&#xff1a; January Monthly GameFi Report在经历了艰难的一年之后&#xff0c;1 月是对加密货币市场最有利的月份。虽然可以说的大部分内容适用于其他看涨周期&#xff0c;但有几个统计数据令 1 月在区块链领域非常有趣。例如&#…

花3个月面过京东测开岗,拿个20K不过分吧?

背景介绍 计算机专业&#xff0c;代码能力一般&#xff0c;之前有过两段实习以及一个学校项目经历。第一份实习是大二暑期在深圳的一家互联网公司做前端开发&#xff0c;第二份实习由于大三暑假回国的时间比较短&#xff08;小于两个月&#xff09;&#xff0c;于是找的实习是在…

GPU并行效率问题——通过MPS提升GPU计算收益

现象描述使用V100_32G型号的GPU运行计算程序时&#xff0c;发现程序每5秒能够完成一次任务&#xff0c;耗费显存6G。鉴于V100 GPU拥有32G的显存&#xff0c;还有很多空闲&#xff0c;决定同时运行多个计算程序&#xff0c;来提升GPU计算收益。然而&#xff0c;这一切都是想当然…

Kotlin新手教程一(Kotlin简介及环境搭建)

目录一、 什么是Kotlin&#xff1f;二、为什么要使用Kotlin&#xff1f;三、使用IntelliJ IDEA搭建Kotlin四、Kotlin使用命令行编译一、 什么是Kotlin&#xff1f; Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言&#xff0c;它也可以被编译成为 JavaScript 源代码&…

IDEA入门安装使用教程

一、背景 作为一个Java开发者&#xff0c;有非常多编辑工具供我们选择&#xff0c;比如Eclipse、IntelliJ IDEA、NetBeans、Visual Studio Code、Sublime Text等等&#xff0c;这些有免费也有收费的&#xff0c;但是就目前市场占比来说普遍使用Eclipse和IntelliJ IDEA这两款主…

字节软件测试岗:惨不忍睹的三面,幸好做足了准备,月薪19k,已拿offer

我今年25岁&#xff0c;专业是电子信息工程本科&#xff0c;19年年末的时候去面试&#xff0c;统一投了测试的岗位&#xff0c;软件硬件都有&#xff0c;那时候面试的两家公司都是做培训的&#xff0c;当初没啥钱&#xff0c;他们以面试为谎言再推荐去培训这点让我特别难受。后…

让huggingface/transformers的AutoTokenizer从本地读词表

https://stackoverflow.com/questions/62472238/autotokenizer-from-pretrained-fails-to-load-locally-saved-pretrained-tokenizer

ArcGIS网络分析之构建网络分析数据集(一)

说明: 1. 本文主要用于演示网络分析服务的搭建过程。所以在此不会深入讨论网络分析服务的每一个细节,本文的目的就是让初学者学会使用网络分析服务进行基本的分析(主要针对后续的WEB开发):路径分析,最近设施点分析,以及服务区分析。 2.关于OD成本矩阵分析,多路径配送,…

如何解决 Python 错误 NameError: name ‘X‘ is not defined

Python“NameError: name is not defined”发生在我们试图访问一个未定义的变量或函数时&#xff0c;或者在它被定义之前。 要解决该错误&#xff0c;需要确保我们没有拼错变量名并在声明后访问它。 确保你没有拼错变量或函数 下面是产生上述错误的示例代码。 employee {na…

【MySQL进阶】SQL优化

&#x1f60a;&#x1f60a;作者简介&#x1f60a;&#x1f60a; &#xff1a; 大家好&#xff0c;我是南瓜籽&#xff0c;一个在校大二学生&#xff0c;我将会持续分享Java相关知识。 &#x1f389;&#x1f389;个人主页&#x1f389;&#x1f389; &#xff1a; 南瓜籽的主页…

论文阅读笔记《Common Visual Pattern Discovery via Spatially Coherent Correspondences》

核心思想 两组点集中共有的匹配区域通常具备两个特点&#xff1a;1.局部的特征相似&#xff1b;2.特征点在空间上的分布也相似。作者将候选匹配点对作为图的节点&#xff0c;将两种相似性统一到边的权重来表示。通过寻找图中稠密连接的子图来寻找两个点集中的匹配区域&#xff…

2023年浙江建筑特种工(施工升降机)真题题库及答案

百分百题库提供特种工&#xff08;施工升降机&#xff09;考试试题、特种工&#xff08;施工升降机&#xff09;考试预测题、特种工&#xff08;施工升降机&#xff09;考试真题、特种工&#xff08;施工升降机&#xff09;证考试题库等,提供在线做题刷题&#xff0c;在线模拟考…