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中的消息会被路由到所有绑定的队列中

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

应用场景:

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

代码示例:

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

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

  • 消费端声明队列、交换机以及绑定
  1. @Configuration
  2. public class ConsumerConfig {
  3. /**
  4. * 交换机名称
  5. */
  6. @Value("${platform.exchange-name}")
  7. private String exchangeName;
  8. /**
  9. * 消费者队列名称(指定队列)
  10. */
  11. @Value("${platform.consumer-queue-name}")
  12. private String queueName;
  13. /**
  14. * 声明持久化队列
  15. * @return
  16. */
  17. @Bean
  18. public Queue consumerQueue(){
  19. return new Queue(queueName,true);
  20. }
  21. /**
  22. * 声明扇形交换机
  23. * @return
  24. */
  25. @Bean
  26. public FanoutExchange fanoutExchange(){
  27. return new FanoutExchange(exchangeName);
  28. }
  29. /**
  30. * 声明队列和交换机的绑定
  31. * @param queue
  32. * @param myexchange
  33. * @return
  34. */
  35. @Bean
  36. public Binding binding(Queue queue, FanoutExchange myexchange) {
  37. return BindingBuilder.bind(queue).to(myexchange);
  38. }
  39. }

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

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

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

Direct Exchange

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



特点:

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

应用场景:

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

代码实现:

  • 生产者发送消息:
  1. @Service
  2. public class Producer {
  3. @Value("${platform.exchange-name}")
  4. private String exchangeName;
  5. @Resource
  6. private RabbitTemplate rabbitTemplate;
  7. public void publishMessage(){
  8. for(int i = 0; i < 100; i++){
  9. rabbitTemplate.convertAndSend(exchangeName,"log.error","发布到绑定routing-key是log.error的队列"+i);
  10. }
  11. for (int i = 100; i < 200; i++) {
  12. rabbitTemplate.convertAndSend(exchangeName,"log.debug","发布到绑定routing-key是log.debug的队列"+i);
  13. }
  14. }
  15. }
  • 声明队列、交换机以及绑定:
  1. @Configuration
  2. public class ConsumerConfig {
  3. /**
  4. * 交换机名称
  5. */
  6. @Value("${platform.exchange-name}")
  7. private String exchangeName;
  8. /**
  9. * 主题名称
  10. */
  11. @Value("${platform.exchange-routing-key}")
  12. private String bindingKey;
  13. /**
  14. * 消费者队列名称(指定队列)
  15. */
  16. @Value("${platform.consumer-queue-name}")
  17. private String queueName;
  18. @Bean
  19. public Queue consumerQueue(){
  20. return new Queue(queueName,true);
  21. }
  22. /**
  23. * 声明直连交换机
  24. * @return
  25. */
  26. @Bean
  27. public DirectExchange directExchange(){
  28. return new DirectExchange(exchangeName);
  29. }
  30. /**
  31. * 绑定队列到直连交换机
  32. * @param queue 队列
  33. * @param myexchange 直连交换机
  34. * @return
  35. */
  36. @Bean
  37. public Binding binding(Queue queue, DirectExchange myexchange) {
  38. return BindingBuilder.bind(queue).to(myexchange).with(bindingKey);
  39. }
  40. }

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

扩展:

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

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

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

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

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

  1. private String exchange = DEFAULT_EXCHANGE;
  2. /** Alias for amq.direct default exchange. */
  3. 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负责处理特定的任务;

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

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

代码实现:

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

上述声明完成以后,可以在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. cka 英文考试题

    ## CKA真题解析 #### 1**Set configuration context $kubectl config use-context k8s. Monitor the logs of Po ...

  2. 任务相关的API函数-uxTaskGetSystemState

    uxTaskGetSystemState:此函数用于获取系统中所有的任务状态,每个任务的状态信息保存在一个TaskStatus_t类型的结构体里面.要使用此函数必须把 configUSE_TRACE_ ...

  3. Linux Access.conf安全配置

    access.conf is the configuration file used to logins to the Linux or Unix systems. This file is loca ...

  4. mysql数据库-简介

    目录 1 MySQL 的三大主要分支 1.1 官方文档 1.2 版本演变 1.3 MySQL 安装方式 1.3.1 RPM包安装Mysql 1.3.2 二进制安装MySQL 1.4 mysql组成 1 ...

  5. 韩小韩 API接口

    官方网址:https://api.vvhan.com/ 天气API接口: https://api.vvhan.com/api/weather Bing每日图片API接口: https://api.vv ...

  6. 友盟umeng消息推送直接复制就能用(纯干货)

    一. 单播推送(unicast) 1.1 图 1.2 代码 1 /** 2 * 根据设备的deviceToken, 去给指定的设备推送消息 3 * 4 * @param deviceToken 单个d ...

  7. Step By Step(C调用Lua)

    Step By Step(C调用Lua) 1. 基础:    Lua的一项重要用途就是作为一种配置语言.现在从一个简单的示例开始吧.    --这里是用Lua代码定义的窗口大小的配置信息    wid ...

  8. a标签点击跳转到新窗口打开目标资源

    点击a标签跳转到新窗口打开目标资源, <a href="http://gd.zjtcn.com/facs/c_t_p1_圆钉 50-75.html" target=" ...

  9. 将代码生成器带入TVM

    将代码生成器带入TVM 为了使数据科学家不必担心开发新模型时的性能,硬件后端提供程序(例如Intel,NVIDIA,ARM等)可以提供诸如cuBLAS或cuDNN之类的内核库以及许多常用的深度学习内核 ...

  10. 【八】Kubernetes 五种资源控制器详细介绍以及功能演示

    一.控制器说明 Pod 的分类: 自主式 Pod:该类型的 Pod 无论是异常退出还是正常退出都不会被创建,也就是说没有对应的管理者. 控制器管理的 Pod:该类型 Pod 在控制器的生命周期里,控制 ...