人生终将是场单人旅途,孤独之前是迷茫,孤独过后是成长。

楔子

本篇是消息队列RabbitMQ的第三弹。

RabbitMQ的入门和RabbitMQ+SpringBoot的整合可以点此链接进去回顾,今天要讲的是RabbitMQ的交换机。

本篇是理解RabbitMQ很重要的一篇,交换机是消息的第一站,只有理解了交换机的分发模式,我们才能知道不同交换机根据什么规则分发消息,才能明白在面对不同业务需求的时候应采用哪种交换机。


祝有好收获,先赞后看,快乐无限。

本文代码: 码云地址 GitHub地址

1. Exchange

先来放上几乎每篇都要出现一遍的我画了好久的RabbitMQ架构图。

前两篇文中我们一直没有显式的去使用Exchange,都是使用的默认Exchange,其实Exchange是一个非常关键的组件,有了它才有了各种消息分发模式。

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

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

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

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

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

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

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

2. Fanout-Exchange

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

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

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


下面是一段代码演示:

    @Bean
public Queue fanout1() {
return new Queue("fanout1");
} @Bean
public Queue fanout2() {
return new Queue("fanout2");
} @Bean
public FanoutExchange fanoutExchange() {
// 三个构造参数:name durable autoDelete
return new FanoutExchange("fanoutExchange", false, false);
} @Bean
public Binding binding1() {
return BindingBuilder.bind(fanout1()).to(fanoutExchange());
} @Bean
public 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("消息发送完毕。");
} @Test
public 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("消息已确认");
} }

这两段代码都很好理解,不再赘述,有遗忘的可以去看RabbitMQ第一弹的内容。

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

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

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

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

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

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

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

3. Direct-Exchange

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

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

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

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

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


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

准备工作:

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

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

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

所以是交换机和队列建立绑定关系的时候设置的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可以使用.将单词分开。


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

准备工作:

    // 主题交换机示例
@Bean
public Queue topicQueue1() {
return new Queue("topicQueue1");
} @Bean
public Queue topicQueue2() {
return new Queue("topicQueue2");
} @Bean
public TopicExchange topicExchange() {
// 三个构造参数:name durable autoDelete
return new TopicExchange("topicExchange", false, false);
} @Bean
public Binding topicBinding1() {
return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("sms.*");
} @Bean
public 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("消息已确认");
} }

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

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

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

后记

周一没更文实在惭愧,去医院抽血了,抽了三管,吃多少才能补回来

RabbitMQ已经更新了三篇了,这三篇的内容有些偏基础,下一篇将会更新高级部分内容:包括防止消息丢失,防止消息重复消费等等内容,希望大家持续关注。


最近这段时间压力挺大,优狐令我八月底之前升级到三级,所以各位读者的赞对我很重要,希望大家能够高抬贵手,帮我一哈~

好了,以上就是本期的全部内容,感谢你能看到这里,欢迎对本文点赞收藏与评论,你们的每个点赞都是我创作的最大动力。

我是耳朵,一个一直想做知识输出的伪文艺程序员,我们下期见。

本文代码:码云地址 GitHub地址

上手了RabbitMQ?再来看看它的交换机(Exchange)吧的更多相关文章

  1. RabbitMQ 入门 (Go) - 4. 使用 Fanout Exchange 做服务发现(上)

    到目前为止,我们项目的结果大致如下: 传感器生成的模拟数据(包含传感器名称.数据.时间戳)是通过传感器在运行时动态创建的 Queue 来发送的.这些 Queue 很难直接被发现. 为了解决这个问题,我 ...

  2. rabbitMq及安装、fanout交换机-分发(发布/订阅)

    <dependency>            <groupId>com.rabbitmq</groupId>            <artifactId& ...

  3. rabbitmq系列五 之主题交换机

    1.主题 在前面的例子中,我们对日志系统进行了改进.使用了direct交换机代替了fanout交换机,从只能盲目的广播消息改进为有可能选择性的接收日志. 尽管直接交换机能够改善我们的日志系统,但是它也 ...

  4. RabbitMQ(二):交换机

    前言 学习自bili尚硅谷-RabbitMQ 发布确认 之前的消息应答,队列持久化是为了保证 -> 消息从rabbitmq队列到消费者的过程中不会丢失:消息持久化则是为了保证 -> 消息从 ...

  5. RabbitMQ消息可靠性、死信交换机、消息堆积问题

    目录 消息可靠性 生产者消息确认 示例 消费者消息确认 示例 死信交换机 例子 高可用问题 消息堆积问题 惰性队列 参考 消息可靠性 确保消息至少被消费了一次(不丢失) 消息丢失的几种情况: 消息在网 ...

  6. rabbitMq可靠消息投递之交换机备份

    //备份队列 @Bean("alternate_queue") public Queue alternate_queue() { return new Queue("al ...

  7. RabbitMQ入门:主题路由器(Topic Exchange)

    上一篇博文中,我们使用direct exchange 代替了fanout exchange,这次我们来看下topic exchange. 一.Topic Exchange介绍 topic exchan ...

  8. 【RabbitMQ】4、三种Exchange模式——订阅、路由、通配符模式

    前两篇博客介绍了两种队列模式,这篇博客介绍订阅.路由和通配符模式,之所以放在一起介绍,是因为这三种模式都是用了Exchange交换机,消息没有直接发送到队列,而是发送到了交换机,经过队列绑定交换机到达 ...

  9. PHP 下基于 php-amqp 扩展的 RabbitMQ 简单用例 (三) -- Header Exchange

    此模式下,消息的routing key 和队列的 routing key 会被完全忽略,而是在交换机推送消息和队列绑定交换机时, 分别为消息和队列设置 headers 属性, 通过匹配消息和队列的 h ...

随机推荐

  1. Dicom文件基本操作

    官方文档 网址:https://github.com/fo-dicom/fo-dicom托管在github上. 官方例子 Dicom文件基本操作 var file = DicomFile.Open(@ ...

  2. 3分钟看懂Python后端必须知道的Django的信号机制!

    概念 django自带一套信号机制来帮助我们在框架的不同位置之间传递信息.也就是说,当某一事件发生时,信号系统可以允许一个或多个发送者(senders)将通知或信号(signals)发送给一组接受者( ...

  3. Java环境变量设置:Path、CLASSPATH、JAVA_HOME的作用分别是什么?

    1.Path 作用是指定命令搜索路径,在i命令行下面执行命令如javac编译java程序时,它会到PATH变量所指定的路径中查找百看是否能找到相应的命令程序.        需要把jdk安装目录下的b ...

  4. 如何在sed中使用变量,两种方法

    第一 在sed条件中是不认识变量取值的 sed '/$x/d' test 所以要想它能够识别变量 sed "/$x/d/" test 方法简单就是把"单引号"变 ...

  5. luogu P6087 [JSOI2015]送礼物 二分 单调队列 决策单调性

    LINK:送礼物 原本想了一个 \(nlog^2\)的做法 然后由于线段树常数过大 T到30. 以为这道题卡\(log^2\)没想到真的有神仙写\(log^2\)的过了 是我常数大了 抱歉. 能过的\ ...

  6. luogu3706 [SDOI2017]硬币游戏

    LINK:硬币游戏 对于40分的暴力 构造出AC自动机 列出转移矩阵 暴力高消.右转上一篇文章. 对于100分 我们不难想到这个矩阵过大 且没有用的节点很多我们最后只要n个节点的答案 其他节点的答案可 ...

  7. Python机器学习及实践_从零开始通往KAGGLE竞赛之路PDF高清完整版免费下载|百度云盘|Python基础教程免费电子书

    点击获取提取码:i5nw Python机器学习及实践面向所有对机器学习与数据挖掘的实践及竞赛感兴趣的读者,从零开始,以Python编程语言为基础,在不涉及大量数学模型与复杂编程知识的前提下,逐步带领读 ...

  8. node.js 使用----相关常用命令总结

    node.js 使用----相关常用命令总结 1. 下载并安装node.js 2. 设置全局模块存放的目录 2.1 查看默认配置 npm config ls 2.2 修改全局模块存放目录 npm co ...

  9. BSGS 学习笔记

    问题:求$a^x\equiv b\ (mod\ p)$的最小正整数解. 这时候就要用到BSGS(拔山盖世)算法.直接进入正题: 设$x=im-n$, 则原式等于$a^{im-n}\equiv b\ ( ...

  10. (转)交叉编译lrzsz

    交叉编译lrzsz 2016-03-20 1. 系统环境: Distributor ID:    Ubuntu Description:    Ubuntu 14.04.1 LTS Release:  ...