• RabbitMQ(三)五种工作模式和Exchange交换机


    前言 

    先来了解RabbitMQ一个重要的概念:Exchange交换机

    1. Exchange概念

    • Exchange:接收消息,并根据路由键转发消息所绑定的队列。

      

    蓝色框:客户端发送消息至交换机,通过路由键路由至指定的队列。
    黄色框:交换机和队列通过路由键有一个绑定的关系。
    绿色框:消费端通过监听队列来接收消息。

    2. 交换机属性

     Name交换机名称
     Type交换机类型——direct、topic、fanout、headers、sharding(此篇不讲)
     Durability是否需要持久化,true为持久化
     Auto Delete当最后一个绑定到Exchange上的队列删除后,自动删除该Exchange
     Internal当前Exchange是否用于RabbitMQ内部使用,默认为false
     Arguments扩展参数,用于扩展AMQP协议自定制化使用

    一、简单模式(Hello Word)

    P代表生产者,C代表消费者,红色代码消息队列。P将消息发送到消息队列,C对消息进行处理。

    生产者:

    @Controller 
    public class ProducerDemo {
    
        @Autowired
        private AmqpTemplate rabbitTemplate;
    
        @RequestMapping("/send")
        @ResponseBody
        public String send() {
            String context = "hello==========" + new Date();
            log.info("Sender : " + context);
            //生产者,正在往hello这个路由规则中发送,由于没有交换机,所以路由规则就是队列名称
            this.rabbitTemplate.convertAndSend("hello", context);
            return "success";
        }
    }

    消费者:

    @Component
    //监听hello这个队列
    @RabbitListener(queues = "hello")
    public class ConsumerDemo {
    
        @RabbitHandler
        public void process(String hello) {
            System.out.println("Receiver  ===================: " + hello);
        }
    }

    二、工作模式

    一个队列有两个消费者。一个队列中一条消息,只能被一个消费者消费。

    在上面的基础中,添加一个消费者就OK了。

    消费者:

    //第1个消费者
    @Component @RabbitListener(queues
    = "hello")//监听hello这个队列 public class ConsumerDemo1{ @RabbitHandler public void process(String hello) { System.out.println("Receiver ===================: " + hello); } }
    //第2个消费者 @Component @RabbitListener(queues = "hello")//监听hello这个队列 public class ConsumerDemo2{ @RabbitHandler public void process(String hello) { System.out.println("Receiver ===================: " + hello); } }

    当两个消费者同时监听一个队列时,他们并不能同时消费一条消息,而是随机消费消息。1,2,3,4,5,6消息来了,consumer1消费了1,3,5;consumer2消费了2,4,6。

    这个数据是随机的哦,别理解为奇偶数。可以自己测试一下。

    三、订阅与发布模式(Fanout)

    • 不处理路由键,只需要简单的将队里绑定到交换机上

    • 生产者将消息不是直接发送到队列,而是发送到X交换机,发送到交换机的消息都会被转发到与该交换机绑定的所有队列上

    • Fanout交换机转发消息是最快的

    定义一个订阅模式的交换机:FanoutExchange交换机。然后创建2个队列helloA,helloB,然后将这两个队列绑定到交换机上面。

    @Configuration
    public class RabbitMQConfig {
    
         @Bean
        public Queue queueA() {
            return new Queue("helloA", true);
        }
    
        @Bean
        public Queue queueB() {
            return new Queue("helloB", true);
        }
    
        //创建一个fanoutExchange交换机
        @Bean
        FanoutExchange fanoutExchange() {
            return new FanoutExchange("fanoutExchange");
        }
    
        //将queueA队列绑定到fanoutExchange交换机上面
        @Bean
        Binding bindingExchangeMessageFanoutA(Queue queueA, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(queueA).to(fanoutExchange);
        }
    
        //将queueB队列绑定到fanoutExchange交换机上面
        @Bean
        Binding bindingExchangeMessageFanoutB(Queue queueB, FanoutExchange fanoutExchange) {
            return BindingBuilder.bind(queueB).to(fanoutExchange);
        }
    
    }

    注意一个细节:bindingExchangeMessageFanoutA这种参数中的queueA与创建队列的方法queueA()名字要相同哦。这样才知道queueA绑定了该交换机哦。

    交换机的名称也同样。fanoutExchange参数的名字和fanoutExchange()名字要一样哦。

    生产者:

    this.rabbitTemplate.convertAndSend("fanoutExchange","", context);

    消费者:

    //第1个消费者
    @Component
    @RabbitListener(queues = "hello")//监听hello这个队列
    public class ConsumerDemo1{
    
        @RabbitHandler
        public void process(String hello) {
            System.out.println("Receiver  ===================: " + hello);
        }
    }
    
    //第2个消费者
    @Component
    @RabbitListener(queues = "hello")//监听hello这个队列
    public class ConsumerDemo2{
    
        @RabbitHandler
        public void process(String hello) {
            System.out.println("Receiver  ===================: " + hello);
        }
    }

    现在生产者发送了一条消息,会发现consumer1,2都会收到。之前不是说过一个队列里面的一条消息,只能被一个消费者消费吗?怎么现在一条消息被两个消费者消费了。

    要知道这里对于生产者来说是只生产了一条消息,但是它发送给了交换机,交换机会根据绑定的队列来发送。

    现在绑定了queueA,queueB队列,所以两个队列里面都有消息了。而消费者关注的也是两个队列,就看到了一条消息被两个消费者消费的情况了。

    四、路由模式(Direct)

    • 所有发送到Direct Exchange的消息被转发到RouteKey中指定的Queue。
    • 消息传递时,RouteKey必须完全匹配才会被队列接收,否则该消息会被抛弃。
    @Configuration
    public class RabbitMQConfig {
        public static final String DIRECT_EXCHANGE = "directExchange";
        public static final String QUEUE_DIRECT_A = "direct.A";
        public static final String QUEUE_DIRECT_B = "direct.B";
    
        //创建一个direct交换机
        @Bean
        DirectExchange directExchange() {
            return new DirectExchange(DIRECT_EXCHANGE);
        }
    
        //创建队列A  
        @Bean
        Queue queueDirectNameA() {
            return new Queue(QUEUE_DIRECT_A);
        }
    
        //创建队列B
        @Bean
        Queue queueDirectNameB() {
            return new Queue(QUEUE_DIRECT_B);
        }
    
        //将direct.A队列绑定到directExchange交换机中,使用direct.a.key作为路由规则
        @Bean
        Binding bindingExchangeMessageDirectA(Queue queueDirectNameA, DirectExchange directExchange) {
            return BindingBuilder.bind(queueDirectNameA).to(directExchange).with("direct.a.key");
        }
    
        //将direct.B队列绑定到directExchange交换机中,使用direct.b.key作为路由规则
        @Bean
        Binding bindingExchangeMessageDirectB(Queue queueDirectNameB, DirectExchange directExchange) {
            return BindingBuilder.bind(queueDirectNameB).to(directExchange).with("direct.b.key");
        }
    }

    消费者:

    @Component
    public class ConsumerDemo {
    
        @RabbitListener(queues = "direct.A")
        @RabbitHandler
        public void processtopicA(String hello) {
            System.out.println("Receiver Exchanges direct.A  ===================: " + hello);
        }
    
        @RabbitListener(queues = "direct.B")
        @RabbitHandler
        public void processtopicB(String hello) {
            System.out.println("Receiver Exchanges direct.B  ===================: " + hello);
        }
    } 

    生产者:

    // 往directExchange交换机中发送消息,使用direct.a.key作为路由规则。
    rabbitTemplate.convertAndSend(RabbitMQConfig.DIRECT_EXCHANGE, "direct.a.key", context);

    direct.A,direct.B 两个队列都绑定了交换机directExchange,但他们的路由规则不同,a队列用了direct.a.key,b队列用了direct.b.key,

    这种情况下,生产者使用direct.a.key作为路由规则,就只有a队列能收到消息,b队列则收不到消息。

    五、主题模式(Topic)

    • 所有发送到Topic Exchange的消息被转发到所有管线RouteKey中指定Topic的Queue上

    • Exchange将RouteKey和某Topic进行模糊匹配,此时队列需要绑定一个Topic

    @Configuration
    public class RabbitMQConfig {
    
      public static final String TOPIC_EXCHANGE = "topicExchange";
    
        public static final String TOPIC_KEY_A = "topic.#";
    
        public static final String TOPIC_KEY_B = "topic.b.key";
    
        public static final String QUEUE_TOPIC_A = "topic.A";
    
        public static final String QUEUE_TOPIC_B = "topic.B";
    
    
        //创建一个topic交换机
        @Bean
        TopicExchange topicExchange() {
            return new TopicExchange(TOPIC_EXCHANGE);
        }
    
        //创建队列A
        @Bean
        Queue queueTopicNameA() {
            return new Queue(QUEUE_TOPIC_A);
        }
    
        //创建队列B
        @Bean
        Queue queueTopicNameB() {
            return new Queue(QUEUE_TOPIC_B);
        }
    
        //队列topic.A绑定交换机并且关联了topic.#正则路由规则。就是说只要topic.开头的,topic.A队列都将收到消息
        @Bean
        Binding bindingExchangeMessageTopicA(Queue queueTopicNameA, TopicExchange topicExchange) {
            return BindingBuilder.bind(queueTopicNameA).to(topicExchange).with(TOPIC_KEY_A);
        }
    
        //队列topic.B绑定交换机并且关联了topic.b.key正则路由规则。就是说必须是topic.b.key,topic.B队列才能收到消息,和directExchange类型一样了。
        @Bean
        Binding bindingExchangeMessageTopicB(Queue queueTopicNameB, TopicExchange topicExchange) {
            return BindingBuilder.bind(queueTopicNameB).to(topicExchange).with(TOPIC_KEY_B);
        }
    
    }

    消费者:

    @Component
    public class ConsumerDemo {
    
        @RabbitListener(queues = "topic.A")
        @RabbitHandler
        public void processtopicA(String hello) {
            System.out.println("Receiver Exchanges topic.A  ===================: " + hello);
        }
    
        @RabbitListener(queues = "topic.B")
        @RabbitHandler
        public void processtopicB(String hello) {
            System.out.println("Receiver Exchanges topic.B  ===================: " + hello);
        }
    }

    生产者:

    @RequestMapping("/topic/send")
    @ResponseBody
    public String sendTopicExchange() {
        String context = "Exchange==topic-->a====== " + new Date();
       // 往topicExchange交换机中发送消息,使用topic.b.key作为路由规则。
       this.rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE, "topic.b.key", context);
        return "success";
    }

    这里发送消息时,往topicExchange这个交换机中发送,并且路由规则为topic.b.key。由于b队列绑定了交换机和路由规则就是它,所以队列b能收到消息。

    但是由于A队列的过滤规则为topic.#,就是说只要topic开头的就的路由规则,交换机就会往这个队列里面发送消息。所以a队列也能收到消息,topic.b.key是topic开头的。

    对于a队列来说,路由规则为topic.adsf,topic.b.key,topic.a等等,a队列都将收到消息,因为它的路由规则就是topic开头就可以。

    六、关系转换

    订阅模式,路由模式,主题模式,他们三种都非常类似。而且主题模式可以随时变成两外两种模式。

    在主题模式下:

    • 当路由规则不为正则表达式的时候,他就和路由模式一样。
    • 当路由规则不为正则表达式,且路由规则一样时,就变成了订阅模式。

    在路由模式下:

    • 当路由规则一样时,就变成了订阅模式。

    简单总结五种模式:

    • 简单模式:生产者,一个消费者,一个队列
    • 工作模式:生产者,多个消费者,一个队列
    • 订阅与发布模式(fanout):生产者,一个交换机(fanoutExchange),没有路由规则,多个消费者,多个队列
    • 路由模式(direct):生产者,一个交换机(directExchange),路由规则,多个消费者,多个队列
    • 主题模式(topic):生产者,一个交换机(topicExchange),模糊匹配路由规则,多个消费者,多个队列

    七、总结

    • 一个队列,一条消息只会被一个消费者消费(有多个消费者的情况也是一样的)。
    • 订阅模式,路由模式,主题模式,他们的相同点就是都使用了交换机,只不过在发送消息给队列时,添加了不同的路由规则。订阅模式没有路由规则,路由模式为完全匹配规则,主题模式有正则表达式,完全匹配规则。
    • 在订阅模式中可以看到一条消息被多个消费者消费了,不违背第一条总结,因为一条消息被发送到了多个队列中去了。
    • 在交换机模式下:队列和路由规则有很大关系
    • 在有交换机的模式下:3,4,5模式下,生产者只用关心交换机与路由规则即可,无需关心队列
    • 消费者不管在什么模式下:永远不用关心交换机和路由规则,消费者永远只关心队列,消费者直接和队列交互

    引用:http://www.baowenwei.com/post/fen-bu-shi/rabbitmqyu-spring-springbootji-cheng-ji-mqde-liu-chong-gong-zuo-mo-shi-san

  • 相关阅读:
    2020-2021-1 20209305 《Linux内核原理与分析》第九周作业
    2020-2021-1 20209305 《Linux内核原理与分析》第八周作业
    2020-2021-1 20209305 《Linux内核原理与分析》第七周作业
    2020-2021-1 20209305 《Linux内核原理与分析》第六周作业
    2020-2021-1 20209305 《Linux内核原理与分析》第五周作业
    2020-2021-1 20209305 《Linux内核原理与分析》第四周作业
    2020-2021-1 20209305 《Linux内核原理与分析》第三周作业
    2020-2021-1 20209305 《Linux内核原理与分析》第二周作业
    2020-2021-1 20209309《Linux内核原理与分析》第十二周作业
    2020-2021-1 20209309《Linux内核原理与分析》第十一周作业
  • 原文地址:https://www.cnblogs.com/caoweixiong/p/12918336.html
Copyright © 2020-2023  润新知