热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

面试官:你说你会RabbitMQ,那聊聊它的交换机(Exchange)吧

推荐阅读:我总结了72份面试题,累计3170页,斩获了30+互联网公司offer(含BATJM)2020首战告捷,这份Java面试神技Plus版,让我成功拿到了阿里、京东、字节跳动

推荐阅读:



  • 我总结了72份面试题,累计3170页,斩获了30+互联网公司offer(含BATJM)



  • 2020首战告捷,这份Java面试神技Plus版,让我成功拿到了阿里、京东、字节跳动等大厂offer



  • 疫情之下,收到美团电话面试(成功拿下offer),附学习路线+刷题库




1. Exchange

先来放上RabbitMQ架构图。

Exchange是一个非常关键的组件,有了它才有了各种消息分发模式。

我先简单说说Exchange有哪几种类型:



  1. fanoutFanout-Exchange会将它接收到的消息发往所有与他绑定的Queue中。



  2. directDirect-Exchange会把它接收到的消息发往与它有绑定关系且Routingkey完全匹配的Queue中(默认)。



  3. topicTopic-Exchange与Direct-Exchange相似,不过Topic-Exchange不需要全匹配,可以部分匹配,它约定:Routingkey为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词)。



  4. headerHeader-Exchange不依赖于RoutingKey或绑定关系来分发消息,而是根据发送的消息内容中的headers属性进行匹配。此模式已经不再使用,本文中也不会去讲,大家知道即可。



本文中我们主要讲前三种Exchange方式,相信凭借着我简练的文字和灵魂的画技给大家好好讲讲,争取老妪能解。

Tip:本文的代码演示直接使用SpringBoot+RabbitMQ的模式。


2. Fanout-Exchange

先来看看Fanout-ExchangeFanout-Exchange又称扇形交换机,这个交换机应该是最容易理解的。

ExchangeQueue建立一个绑定关系,Exchange会分发给所有和它有绑定关系的Queue中,绑定了十个Queue就把消息复制十份进行分发。

这种绑定关系为了效率肯定都会维护一张表,从算法效率上来说一般是O(1),所以Fanout-Exchange是这几个交换机中查找需要被分发队列最快的交换机。



下面是一段代码演示:

@Beanpublic Queue fanout1() {return new Queue("fanout1");}@Beanpublic Queue fanout2() {return new Queue("fanout2");}@Beanpublic FanoutExchange fanoutExchange() {// 三个构造参数:name durable autoDeletereturn new FanoutExchange("fanoutExchange", false, false);}@Beanpublic Binding binding1() {return BindingBuilder.bind(fanout1()).to(fanoutExchange());}@Beanpublic Binding binding2() {return BindingBuilder.bind(fanout2()).to(fanoutExchange());}

为了清晰明了,我新建了两个演示用的队列,然后建了一个FanoutExchange,最后给他们都设置上绑定关系,这样一组队列和交换机的绑定设置就算完成了。

紧接着编写一下生产者和消费者:

public void sendFanout() {Client client = new Client();// 应读者要求,以后代码打印的地方都会改成log方式,这是一种良好的编程习惯,用System.out.println一般是不推荐的。log.info("Message content : " + client);rabbitTemplate.convertAndSend("fanoutExchange",null,client);System.out.println("消息发送完毕。");}@Testpublic void sendFanoutMessage() {rabbitProduce.sendFanout();}

@Slf4j
@Component("rabbitFanoutConsumer")
public class RabbitFanoutConsumer {@RabbitListener(queues = "fanout1")public void onMessage1(Message message, Channel channel) throws Exception {log.info("Message content : " + message);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);log.info("消息已确认");}@RabbitListener(queues = "fanout2")public void onMessage2(Message message, Channel channel) throws Exception {log.info("Message content : " + message);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);log.info("消息已确认");}}

这两段代码都很好理解,不再赘述。

其中发送消息的代码有三个参数,第一个参数是Exchange的名称,第二个参数是routingKey的名称,这个参数在扇形交换机里面用不到,在其他两个交换机类型里面会用到。

代码的准备到此结束,我们可以运行发送方法之后run一下了~

项目启动后,我们可以先来观察一下队列与交换机的绑定关系有没有生效,我们在RabbitMQ控制台使用rabbitmqctl list_bindings命令查看绑定关系。

关键部分我用红框标记了起来,这就代表着名叫fanoutExchange的交换机绑定着两个队列,一个叫fanout1,另一个叫fanout2

紧接着,我们来看控制台的打印情况:

可以看到,一条信息发送出去之后,两个队列都接收到了这条消息,紧接着由我们的两个消费者消费。

Tip: 如果你的演示应用启动之后没有消费信息,可以尝试重新运行一次生产者的方法发送消息。


3. Direct-Exchange

Direct-Exchange是一种精准匹配的交换机,我们之前一直使用默认的交换机,其实默认的交换机就是Direct类型。

如果将Direct交换机都比作一所公寓的管理员,那么队列就是里面的住户。(绑定关系)

管理员每天都会收到各种各样的信件(消息),这些信件的地址不光要标明地址(ExchangeKey)还需要标明要送往哪一户(routingKey),不然消息无法投递。

以上图为例,准备一条消息发往名为SendService的直接交换机中去,这个交换机主要是用来做发送服务,所以其绑定了两个队列,SMS队列和MAIL队列,用于发送短信和邮件。

我们的消息除了指定ExchangeKey还需要指定routingKeyroutingKey对应着最终要发送的是哪个队列,我们的示例中的routingKey是sms,这里这条消息就会交给SMS队列。



听了上面这段,可能大家对routingKey还不是很理解,我们上段代码实践一下,大家应该就明白了。

准备工作:

@Beanpublic Queue directQueue1() {return new Queue("directQueue1");}@Beanpublic Queue directQueue2() {return new Queue("directQueue2");}@Beanpublic DirectExchange directExchange() {// 三个构造参数:name durable autoDeletereturn new DirectExchange("directExchange", false, false);}@Beanpublic Binding directBinding1() {return BindingBuilder.bind(directQueue1()).to(directExchange()).with("sms");}@Beanpublic Binding directBinding2() {return BindingBuilder.bind(directQueue2()).to(directExchange()).with("mail");}

新建两个队列,新建了一个直接交换机,并设置了绑定关系。

这里的示例代码和上面扇形交换机的代码很像,唯一可以说不同的就是绑定的时候多调用了一个withroutingKey设置了上去。

所以是交换机和队列建立绑定关系的时候设置的routingKey,一个消息到达交换机之后,交换机通过消息上带来的routingKey找到自己与队列建立绑定关系时设置的routingKey,然后将消息分发到这个队列去。

生产者:

public void sendDirect() {Client client = new Client();log.info("Message content : " + client);rabbitTemplate.convertAndSend("directExchange","sms",client);System.out.println("消息发送完毕。");}

消费者:

@Slf4j
@Component("rabbitDirectConsumer")
public class RabbitDirectConsumer {@RabbitListener(queues = "directQueue1")public void onMessage1(Message message, Channel channel) throws Exception {log.info("Message content : " + message);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);log.info("消息已确认");}@RabbitListener(queues = "directQueue2")public void onMessage2(Message message, Channel channel) throws Exception {log.info("Message content : " + message);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);log.info("消息已确认");}}

效果图如下:

只有一个消费者进行了消息,符合我们的预期。


4. Topic-Exchange

Topic-Exchange是直接交换机的模糊匹配版本,Topic类型的交换器,支持使用"*“和”#"通配符定义模糊bindingKey,然后按照routingKey进行模糊匹配队列进行分发。



  • *:能够模糊匹配一个单词。



  • #:能够模糊匹配零个或多个单词。



因为加入了两个通配定义符,所以Topic交换机的routingKey也有些变化,routingKey可以使用.将单词分开。



这里我们直接来用一个例子说明会更加的清晰:

准备工作:

// 主题交换机示例@Beanpublic Queue topicQueue1() {return new Queue("topicQueue1");}@Beanpublic Queue topicQueue2() {return new Queue("topicQueue2");}@Beanpublic TopicExchange topicExchange() {// 三个构造参数:name durable autoDeletereturn new TopicExchange("topicExchange", false, false);}@Beanpublic Binding topicBinding1() {return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("sms.*");}@Beanpublic Binding topicBinding2() {return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("mail.#");}

新建两个队列,新建了一个Topic交换机,并设置了绑定关系。

这里的示例代码我们主要看设置routingKey,这里的routingKey用上了通配符,且中间用.隔开,这就代表topicQueue1消费sms开头的消息,topicQueue2消费mail开头的消息,具体不同往下看。

生产者:

public void sendTopic() {Client client = new Client();log.info("Message content : " + client);rabbitTemplate.convertAndSend("topicExchange","sms.liantong",client);System.out.println("消息发送完毕。");}

消费者:

@Slf4j
@Component("rabbitTopicConsumer")
public class RabbitTopicConsumer {@RabbitListener(queues = "topicQueue1")public void onMessage1(Message message, Channel channel) throws Exception {log.info("Message content : " + message);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);log.info("消息已确认");}@RabbitListener(queues = "topicQueue2")public void onMessage2(Message message, Channel channel) throws Exception {log.info("Message content : " + message);channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);log.info("消息已确认");}}

这里我们的生产者发送的消息routingKeysms.liantong,它就会被发到topicQueue1队列中去,这里消息的routingKey也需要用.隔离开,用其他符号无法正确识别。

如果我们的routingKeysms.123.liantong,那么它将无法找到对应的队列,因为topicQueue1的模糊匹配用的通配符是*而不是#,只有#是可以匹配多个单词的。

Topic-ExchangeDirect-Exchange很相似,我就不再赘述了,通配符*#的区别也很简单,大家可以自己试一下。


推荐阅读
  • PHP中元素的计量单位是什么? ... [详细]
  • 本文作为“实现简易版Spring系列”的第五篇,继前文深入探讨了Spring框架的核心技术之一——控制反转(IoC)之后,将重点转向另一个关键技术——面向切面编程(AOP)。对于使用Spring框架进行开发的开发者来说,AOP是一个不可或缺的概念。了解AOP的背景及其基本原理,对于掌握这一技术至关重要。本文将通过具体示例,详细解析AOP的实现机制,帮助读者更好地理解和应用这一技术。 ... [详细]
  • Java 零基础入门:SQL Server 学习笔记(第21篇)
    Java 零基础入门:SQL Server 学习笔记(第21篇) ... [详细]
  • 2019年后蚂蚁集团与拼多多面试经验详述与深度剖析
    2019年后蚂蚁集团与拼多多面试经验详述与深度剖析 ... [详细]
  • MongoDB Aggregates.group() 方法详解与编程实例 ... [详细]
  • Spring Batch 异常处理与任务限制优化策略 ... [详细]
  • 如何使用 net.sf.extjwnl.data.Word 类及其代码示例详解 ... [详细]
  • 计算 n 叉树中各节点子树的叶节点数量分析 ... [详细]
  • 本文深入探讨了 MXOTDLL.dll 在 C# 环境中的应用与优化策略。针对近期公司从某生物技术供应商采购的指纹识别设备,该设备提供的 DLL 文件是用 C 语言编写的。为了更好地集成到现有的 C# 系统中,我们对原生的 C 语言 DLL 进行了封装,并利用 C# 的互操作性功能实现了高效调用。此外,文章还详细分析了在实际应用中可能遇到的性能瓶颈,并提出了一系列优化措施,以确保系统的稳定性和高效运行。 ... [详细]
  • 在Java中,匿名函数作为一种无名的函数结构,无法独立调用;而在JavaScript中,不仅有类似的匿名函数,还有立即执行函数(IIFE)和闭包等高级特性。立即执行函数同样基于匿名函数实现,但会在定义时立即执行,而闭包则通过嵌套函数来捕获外部变量,实现数据封装和持久化。这些不同的函数形式在实际开发中各有应用场景,理解其特点有助于更好地利用语言特性进行编程。 ... [详细]
  • Ceph API微服务实现RBD块设备的高效创建与安全删除
    本文旨在实现Ceph块存储中RBD块设备的高效创建与安全删除功能。开发环境为CentOS 7,使用 IntelliJ IDEA 进行开发。首先介绍了 librbd 的基本概念及其在 Ceph 中的作用,随后详细描述了项目 Gradle 配置的优化过程,确保了开发环境的稳定性和兼容性。通过这一系列步骤,我们成功实现了 RBD 块设备的快速创建与安全删除,提升了系统的整体性能和可靠性。 ... [详细]
  • Spring框架入门指南:专为新手打造的详细学习笔记
    Spring框架是Java Web开发中广泛应用的轻量级应用框架,以其卓越的功能和出色的性能赢得了广大开发者的青睐。本文为初学者提供了详尽的学习指南,涵盖基础概念、核心组件及实际应用案例,帮助新手快速掌握Spring框架的核心技术与实践技巧。 ... [详细]
  • 本文详细介绍了如何在Linux系统中搭建51单片机的开发与编程环境,重点讲解了使用Makefile进行项目管理的方法。首先,文章指导读者安装SDCC(Small Device C Compiler),这是一个专为小型设备设计的C语言编译器,适合用于51单片机的开发。随后,通过具体的实例演示了如何配置Makefile文件,以实现代码的自动化编译与链接过程,从而提高开发效率。此外,还提供了常见问题的解决方案及优化建议,帮助开发者快速上手并解决实际开发中可能遇到的技术难题。 ... [详细]
  • 在 Android 开发中,通过合理利用系统通知服务,可以显著提升应用的用户交互体验。针对 Android 8.0 及以上版本,开发者需首先创建并注册通知渠道。本文将详细介绍如何在应用中实现这一功能,包括初始化通知管理器、创建通知渠道以及发送通知的具体步骤,帮助开发者更好地理解和应用这些技术细节。 ... [详细]
  • 当前,众多初创企业对全栈工程师的需求日益增长,但市场中却存在大量所谓的“伪全栈工程师”,尤其是那些仅掌握了Node.js技能的前端开发人员。本文旨在深入探讨全栈工程师在现代技术生态中的真实角色与价值,澄清对这一角色的误解,并强调真正的全栈工程师应具备全面的技术栈和综合解决问题的能力。 ... [详细]
author-avatar
Stupid锋_891
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有