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

猜你喜欢

转载自www.cnblogs.com/caoweixiong/p/12918336.html