RabbitMQ中,生产者并不是直接将消息发送给queue,而是先将消息发送给exchange,再由exchange通过不同的路由规则将消息路由到绑定的队列中进行存储,那么为什么要先将消息发送给exchange而不是直接发送给queue呢?

理解Exchange

为什么要在生产者和queue之间多一个exchange呢?

我们知道RabbitMQ是AMQP协议的一个实现,生产者和消费者解耦合是AMQP协议的核心思想,生产者不需要知道消息被发送到哪些队列中,只需要将消息发送到exchange即可。先由exchange来接收生产者发送的消息,然后exchange按照routing-key和Binding规则将消息路由到对应的队列中,exchange就相当于一个分发消息的交换机。

在这种模式下,生产者只面向exhange,exchange根据routing-key和binding路由消息到queue,消费者只面向对应的queue,以此来将消息传递的各个层面拆分开,从而降低整体的耦合度。

理解Routing-Key和Binding

exchange收到生产者发送的消息后,如何路由到queue呢,此时就需要用到routing-key和binding。

binding:exchange和queue之间的关系,也就是说使用binding关联的队列只对当前交换机上消息感兴趣。

routing-key:在绑定exchange和queue时可以添加routing-key,routing-key是一个消息的一个属性,这个属性决定了交换机如何将消息路由到队列。

可以说,binding和routing-key一起决定了exchange将消息路由到哪些队列中,当然路由的算法还取决于exchange的类型。

Exchange类型

exchange主要有以下几种分类: fanout exchange、direct exchange、topic exchange、headers exchange,我们主要介绍前面三种交换机。

Fanout Exchange

fanout exchange也可以叫做扇形交换机,示意图如下:

特点:

发布消息时routing-key被忽略

生产者发送到exchange中的消息会被路由到所有绑定的队列中

由于扇形交换机会将消息路由给所有绑定的队列的特性,扇形交换机是作为广播路由的理想选择。

应用场景:

对同样的消息做不同的操作,比如同样的数据,既要存数据库,又要存储到磁盘。

代码示例:

  • 生产者发送消息到交换机
@Service
public class Producer { @Value("${platform.exchange-name}")
private String exchangeName; @Resource
private RabbitTemplate rabbitTemplate; public void publishMessage(){
for(int i = 0; i < 100; i++){
rabbitTemplate.convertAndSend(exchangeName,"","发布消息========>"+i);
}
}
}

convertAndSend方法的第二个参数就是routing-key,此时设置为空字符串即可。

  • 消费端声明队列、交换机以及绑定
@Configuration
public class ConsumerConfig {
/**
* 交换机名称
*/
@Value("${platform.exchange-name}")
private String exchangeName; /**
* 消费者队列名称(指定队列)
*/
@Value("${platform.consumer-queue-name}")
private String queueName; /**
* 声明持久化队列
* @return
*/
@Bean
public Queue consumerQueue(){
return new Queue(queueName,true);
} /**
* 声明扇形交换机
* @return
*/
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange(exchangeName);
} /**
* 声明队列和交换机的绑定
* @param queue
* @param myexchange
* @return
*/
@Bean
public Binding binding(Queue queue, FanoutExchange myexchange) {
return BindingBuilder.bind(queue).to(myexchange);
}
}

上述声明中,篇幅所限只声明了一个队列,生产使用时可以声明多个队列,并且和交换机进行绑定。

声明完队列、交换机以及绑定之后就可以启动生产者和消费者发送消息,此时就可以看到同样的消息发送到了多个绑定的队列中。

具体代码可以参考码云fanout生产者fanout消费者

Direct Exchange

直连交换机,RabbitMQ默认的交换机就是直连交换机,示意图如下所示:



特点:

生产者发布消息时必须带着routing-key,队列绑定到交换机时必须指定binding-key ,且routing-key和binding-key必须完全相同,如此才能将消息路由到队列中。

应用场景:

直连交换机通常用来循环分发任务给多个workers,例如在一个日志处理系统中,一个worker处理error级别日志,另外一个worker用来处理info级别的日志,此时生产者只需要在发送时指定特定的routing-key即可,绑定队列时binding-key只需要和routing-key保持一致即可接收到特定的消息。

代码实现:

  • 生产者发送消息:
@Service
public class Producer { @Value("${platform.exchange-name}")
private String exchangeName; @Resource
private RabbitTemplate rabbitTemplate; public void publishMessage(){
for(int i = 0; i < 100; i++){
rabbitTemplate.convertAndSend(exchangeName,"log.error","发布到绑定routing-key是log.error的队列"+i);
} for (int i = 100; i < 200; i++) {
rabbitTemplate.convertAndSend(exchangeName,"log.debug","发布到绑定routing-key是log.debug的队列"+i);
}
}
}
  • 声明队列、交换机以及绑定:
@Configuration
public class ConsumerConfig {
/**
* 交换机名称
*/
@Value("${platform.exchange-name}")
private String exchangeName; /**
* 主题名称
*/
@Value("${platform.exchange-routing-key}")
private String bindingKey; /**
* 消费者队列名称(指定队列)
*/
@Value("${platform.consumer-queue-name}")
private String queueName; @Bean
public Queue consumerQueue(){
return new Queue(queueName,true);
} /**
* 声明直连交换机
* @return
*/
@Bean
public DirectExchange directExchange(){
return new DirectExchange(exchangeName);
} /**
* 绑定队列到直连交换机
* @param queue 队列
* @param myexchange 直连交换机
* @return
*/
@Bean
public Binding binding(Queue queue, DirectExchange myexchange) {
return BindingBuilder.bind(queue).to(myexchange).with(bindingKey);
}
}

使用不同的binding-key绑定队列到直连交换机,发送消息时只需要指定对应的routing-key就可以将消息发送到对应的队列中,此时启动生产者和消费者,发送消息后就可以看到不同的数据进入了对应的队列中,更多代码请参考码云direct生产者direct消费者

扩展:

前面说到RabbitMQ使用的默认交换机是直连交换机,此处我们从源码上来确认一下,代码入口如下所示:

rabbitTemplate.convertAndSend(queueName,"消息"+i);

点进convertAndSend方法后可以看到如下所示的代码:

@Override
public void convertAndSend(String routingKey, final Object object) throws AmqpException {
convertAndSend(this.exchange, routingKey, object, (CorrelationData) null);
}

可以看到此处给了一个exchange参数,在当前类中可以找到这个exchange参数对应的声明:

private String exchange = DEFAULT_EXCHANGE;

/** Alias for amq.direct default exchange. */
private static final String DEFAULT_EXCHANGE = "";

从DEFAULT_EXCHANGE的注释可以看出来默认的交换机是直连交换机。

默认交换机中的routing-key是队列的名称,当队列没有明确指定绑定到某个交换机上时,默认会以队列名称作为binding-key绑定到默认交换机上,因为发送消息时的routing-key是队列名称,队列绑定默认交换机时的binding-key也是队列名称,因此默认交换机会将消息路由到对应的队列中。

Topic Exchange

主题交换机,一种支持灵活配置routing-key的交换机,示意图如下所示:

特点:

routing-key必须由多个单词或者通配符组成,单词或者通配符之间使用.隔开,上限为255个字节;

*通配符只能匹配一个单词;

#通配符可以匹配零个或者多个单词;

队列绑定交换机时的binding-key要能够匹配发送消息时的routing-key才能将消息路由到对应的队列;

根据routing-key和binding-key的匹配情况,消息可能进入单个队列,也可能进入多个队列,也可能丢失;

主题队列的routing-key设置为#时,表示所有所有的队列都可以接收到消息,相当于fanout交换机;

主题队列的routing-key中不包含#或者*时,表示指定队列可以接收到消息,相当于direct交换机;

匹配例子:

routing-key binding-key 是否匹配
*.orange.* quick.orange.rabbit true
*.orange.* quick.red.rabbit false
*.*.rabbit quick.red.rabbit true
*.*.rabbit a.quick.red.rabbit false
lazy.# lazy.red.rabbit true
lazy.# lazy.red.rabbit.a.b true

应用场景:

由多个workers完成的后台任务,每个worker负责处理特定的任务;

涉及分类或者标签的数据处理;

云端不同种类服务的协调;

代码实现:

  • 生产者发送数据:
@Service
public class Producer { @Value("${platform.exchange-name}")
private String exchangeName; @Resource
private RabbitTemplate rabbitTemplate; public void publishMessage(){
for(int i = 0; i < 100; i++){
if(i%2==0){
rabbitTemplate.convertAndSend(exchangeName,"gz.log.error","消息==>"+i);
}else{
rabbitTemplate.convertAndSend(exchangeName,"zj.log.info.a","消息==>"+i);
}
}
}
}
  • 声明队列、交换机以及绑定:
@Configuration
public class ConsumerConfig { @Value("${platform.exchange-name}")
private String exchangeName; @Value("${platform.consumer-queue-name}")
private String queueName; /**
* gz.*.* | *.log.#
*/
@Value("${platform.exchange-routing-key}")
private String bindingKey; @Bean
public TopicExchange topicExchange(){
return new TopicExchange(exchangeName);
} @Bean
public Queue consumerQueue(){
return new Queue(queueName,true);
} @Bean
public Binding binding(Queue queue, TopicExchange topicExchange){
return BindingBuilder.bind(queue).to(topicExchange).with(bindingKey);
}
}

上述声明完成以后,可以在rabbitmq的管理页面查看到如下所示的结果:



生产者设置的routing-key是gz.log.error和zj.log.info.a,两个队列的binding-key分别为gz.*.* 和*.log.#,gz.*.* 只能匹配gz.log.error,*.log.#可以匹配两个routing-key,因此绑定的两个队列,一个可以获取到全部数据,一个只能获取到部分数据,结果如下:

具体代码实现参考码云topic生产者topic消费者

总结

上面主要介绍三种类型的交换机,fanout交换机忽略routing-key,可以将消息发送到所有绑定的队列中,direct交换机需要指定routing-key,且必须和binding-key完全一致才可以发送消息到绑定队列中,最灵活的则为topic交换机,可以通过通配符的方式进行匹配,根据匹配结果将消息发送到不同队列中,其实还有header交换机,不过应用较少且本人也未进行研究过,此处忽略不记。

RabbitMQ交换机的更多相关文章

  1. 认识RabbitMQ交换机模型

    前言 RabbitMQ是消息队列中间件(Message Queue Middleware)中一种,工作虽然有用到,但是却没有形成很好的整体包括,主要是一些基础概念的认识,这里通过阅读<Rabbi ...

  2. 关于RabbitMQ交换机的理解

    RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性.扩展性.高可用性等方面表现不俗.消息中间件主要用于组件之间的解耦,消 ...

  3. RabbitMQ交换机规则实例

    RabbitMQ Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct.fanout.topic.headers .headers 匹配 AMQP 消息的 header ...

  4. RabbitMQ交换机、RabbitMQ整合springCloud

    目标 1.交换机 2.RabbitMQ整合springCloud 交换机 蓝色区域===生产者 红色区域===Server:又称Broker,接受客户端的连接,实现AMQP实体服务 绿色区域===消费 ...

  5. Rabbitmq交换机三种模式介绍

    1.topic 将路由键和某模式进行匹配.此时队列需要绑定要一个模式上.符号“#”匹配一个或多个词,符号“*”匹配不多不少一个词.因此“abc.#”能够匹配到“abc.def.ghi”,但是“abc. ...

  6. 使用代码创建rabbitmq交换机和队列绑定

    1.获取channel对象 2.声明(创建)对列 // 第一个参数,queueName:对列名称.数据类型:String// 第二个参数,durable:是否持久化, 队列的声明默认是存放到内存中的, ...

  7. 【RabbitMQ-7】RabbitMQ—交换机标识符

    死信队列概念 死信队列(Dead Letter Exchange),死信交换器.当业务队列中的消息被拒绝或者过期或者超过队列的最大长度时,消息会被丢弃,但若是配置了死信队列,那么消息可以被重新发布到另 ...

  8. rabbitMq交换机direct、topics

    一: direct 上面我用采用了广播的模式进行消息的发送,现在我们采用路由的方式对不同的消息进行过滤 发送端代码 public class RoutingSendDirect { private s ...

  9. RabbitMQ 交换机类型

    1,扇形交换机 fanout 2, 直连交换机 direct 3, 通配符交换机 topic

随机推荐

  1. mpstat命令

    mpstat命令 mpstat命令指令主要用于多CPU环境下,它显示各个可用CPU的状态系你想.这些信息存放在/proc/stat文件中.在多CPUs系统里,其不但能查看所有CPU的平均状况信息,而且 ...

  2. IT菜鸟之思科模拟实验(PT)

    思科官方的模拟软件:cisco packet tracer 网卡端口类型: Ethernet(以太网) 十兆 FastEthernet 百兆 GigabitEthernet 千兆 交换机的端口默认都是 ...

  3. redis 处理缓存穿透

    1. 缓存穿透简述 举例说明,redis中确实没有key值为"redis"数据,并且数据库里面也没有,那么每一次都会穿过缓存层,会将请求打到数据库查询,然后数据库进行查询,造成了不 ...

  4. Linux 中的 守护进程

    什么是守护进程 脱离控制终端的,运行于后端的进程,由系统管理的,按计划自动启动/停止/重启,用以执行特定的任务. 为什么要有守护进程? 在某些需求场景下,我们希望某项系统任务能够按计划按预期,始终/自 ...

  5. DDD中聚合、聚合根的含义以及作用

    聚合与聚合根的含义 聚合: 聚合往往是一些实体为了某项业务而聚类在一起形成的集合 , 举个例子, 社会是由一个个的个体组成的,象征着我们每一个人.随着社会的发展,慢慢出现了社团.机构.部门等组织,我们 ...

  6. 重新整理 .net core 实践篇—————日志系统之结构化[十八]

    前言 什么是结构化呢? 结构化,就是将原本没有规律的东西进行有规律话. 就比如我们学习数据结构,需要学习排序然后又要学习查询,说白了这就是一套,没有排序,谈如何查询是没有意义的,因为查询算法就是根据某 ...

  7. spring如何集成第三方框架? 比如mybatis

    实体Bean的创建: 1: 基于class构建, 2: 构造方法构建 3: 静态工厂方法创建 4: FactoryBean构建 spring如何集成第三方框架? 比如mybatis 在mybatis中 ...

  8. 保姆级尚硅谷SpringCloud学习笔记(更新中)

    目录 前言 正文内容 001_课程说明 002_零基础微服务架构理论入门 微服务优缺点[^1] SpringCloud与微服务的关系 SpringCloud技术栈 003_第二季Boot和Cloud版 ...

  9. 实验8、31个最重要的Python Flask面试问题和答案

    实验介绍 1. 实验内容 内容涵盖了31个最热门的Flask面试问题,帮助学生更好的理解Flask. 2. 实验要点 了解面试Flask开发人员的常见问题 实验内容 Flask面试问答 Q:Flask ...

  10. 【NX二次开发】Block UI 枚举

    属性: 常规         类型 描述     BlockID     String 控件ID     Enable     Logical 是否可操作     Group     Logical ...