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

楔子

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

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

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


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

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

1. Exchange

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

前两篇文中我们一直没有显式的去使用Exchange,都是使用的默认Exchange,其实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是这几个交换机中查找需要被分发队列最快的交换机。


下面是一段代码演示:

    @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还需要指定routingKeyroutingKey对应着最终要发送的是哪个队列,我们的示例中的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");
}

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

这里的示例代码和上面扇形交换机的代码很像,唯一可以说不同的就是绑定的时候多调用了一个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可以使用.将单词分开。


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

准备工作:

    // 主题交换机示例
@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("消息已确认");
} }

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

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

Topic-ExchangeDirect-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. Ubuntu Server 19.04配置静态IP

    这个/etc/netplan下默认有个文件50-cloud-init.yaml,直接修改它就行了 sudo vim /etc/netplan/50-cloud-init.yaml 网口名字enp0s3 ...

  2. Spring 与 MyBatis 事务管理源码解析

    用到mybatis便由spring和myabtis集成,SqlSessionFactoryBean(直接负责对mybatis所需环境的创建) ,配置相应的datasource到springConfig ...

  3. python学习笔记1 -- 函数式编程之高阶函数 filter

    filter 函数用于过滤序列,与map 和reduce函数类似,作为高阶函数,他们也是同样的使用方法,filter(参数1, 参数2),参数1是一个函数,而参数2是一个序列. filter的作用是根 ...

  4. pycharm 退出虚拟环境

    pycharm 内置虚拟环境 venv 如果要退出就直接 deactivate 命令就行 运行的话直接在命令行输python3  文件名

  5. Markdown简洁语法说明

    学于黑马和传智播客联合做的教学项目 感谢 黑马官网 传智播客官网 微信搜索"艺术行者",关注并回复关键词"乐优商城"获取视频和教程资料! b站在线视频 0.前言 ...

  6. JavaScript高级程序设计(第三版) 5/25

    第三章 基本概念 1.任何语言的核心都必然会描述这门语言最基本的工作原理.而描述的内容通常都要涉及这门语言的语法.操作符.数据类型.内置功能等用于构建复杂解决方案的基本概念. 2.浮点数值,该数值中必 ...

  7. 线程_使用multiprocessing启动一个子进程及创建Process 的子类

    from multiprocessing import Process import os # 子进程执行的函数 def run_proc(name): print("子进程运行中,名称:% ...

  8. PHP strnatcmp() 函数

    实例 使用"自然"算法来比较两个字符串(区分大小写): <?php高佣联盟 www.cgewang.comecho strnatcmp("2Hello world! ...

  9. P4491 [HAOI2018]染色 广义容斥 NTT 生成函数

    LINK:染色 算是比较常规的广义容斥. 算恰好k个 可以直接转成至少k个. 至少k个非常的好求 直接生成函数. 设\(g_k\)表示至少有k个颜色是满足的 那么有 \(g_k=C(m,k)\frac ...

  10. linux条件测试操作(test)和if判断语句,while循环语句,break控制语句和for循环和case多分枝语句和select语句

    条件测试操作 条件测试是专为影响"$?"的操作,是条件转移.循环语句的基础   test测试命令: test 用途:测试特定的表达式是否成立,当条件成立时,命令执行后的返回值为0, ...