RabbitMQ 入门之发布订阅模式

147 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

RabbitMQ 的结构

RabbitMQ 有几个个概念:

  • RoutingKey 路由键,在发布订阅模型中根据 RoutingKey 可以找到具体队列。
  • Publisher 生产者,生产消息发送给 Exchange(如果没有 Exchange 就直接发送发到 Queue)。
  • Exchange 交换机,根据与 Queue 绑定的 BindingKey 将消息发送到 Queue(BindingKey 与 RoutingKey 概念相似)。
  • Queue 队列,Exchange 将消息发送到 Queue 后,消息暂存在 Queue 内,等待 消费者消费。
  • Consumer 消费者,监听具体队列,从队列中获取消息后消费消息。

总结:生产者面向 Exchange 和 Queue 编程。消费者面向Queue 编程。

RabbitMQ 的六种模式

RabbitMQ 有六种模式:

  • 简单模式:无交换机,一个队列只能连接一个消费者。
  • 工作模式:无交换机,一条队列能有连接多个消费者。
  • 广播模式:交换机将接收到的消息路由到每一个跟其绑定的队列。
  • 路由模式:交换机将接收的消息根据规则路由到指定的队列。
  • 主题模式:交换机将接收到的消息,根据匹配路由到队列。路由键必须是多个单词的列表,使用 ‘.’ 分割。
  • RPC 模式:调用远程方法需要等待结果时可用。

发布订阅模式包含了广播模式、路由模式和主题模式。都是多个生产者多个消费者模式,通过 RoutingKey 来进行路由。

SpringAMQP

SpringAMQP 是在 Spring 框架中整合了 AMQP 规则,可以进行队列配置操作。

声明队列和交换机,并将两者进行绑定。有两种方式,第一种 @Bean 进行类配置,第二种 @RabbitListener 进行注解配置。

类配置方式:

    // 声明 FanoutExchange 交换机
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("itcast.fanout");
    }
    // 声明队列
    @Bean
    public Queue fanoutQueue() {
        return new Queue("fanout.queue");
    }
    // 绑定队列和交换机
    @Bean
    public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueue).to(fanoutExchange);
    }

注解方式:

	@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2"), // 监听队列
            exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT), // 交换器名称和交换器的类型
            key = {"red", "yellow"} // 路由键
    ))

Fanout Exchange 广播模式

广播模式,声明交换机和队列,将交换器与队列绑定。将消息发送到所有与之绑定的队列中。

	// 声明 FanoutExchange 交换机
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("itcast.fanout");
    }
    // 声明第一个队列
    @Bean
    public Queue fanoutQueue1() {
        return new Queue("fanout.queue1");
    }
    // 声明第二个队列
    @Bean
    public Queue fanoutQueue2() {
        return new Queue("fanout.queue2");
    }
    // 绑定队列1和交换机
    @Bean
    public Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }
    // 绑定队列2和交换机
    @Bean
    public Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }

	// consumer 声明监听队列
	@RabbitListener(queues = "fanout.queue1")
    public void listenFanoutQueue1(String message) {
        System.out.println("消费者接收到fanout.queue1消息:【" + message + "】");
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    // 测试给 itcast.fanout 队列发送消息。
    @Test
    public void testSendFanoutExchange() {
        // 交换机名称
        String exchangeName = "itcast.fanout";
        // 消息
        String msg = "hello, everyone";
        rabbitTemplate.convertAndSend(exchangeName, "", msg);
    }

Direct Exchange 路由模式

路由模式,声明交换机和队列,将交换器与队列绑定。将消息发送到与之绑定的队列中,一般只绑定一个队列,实现精准匹配。Direct Exchange 也可以模拟 Fanout Exchange。Exchange 与 Queue 的 BindingKey 相同即可(RoutingKey 相同)。

	// consumer
	@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"), // 监听队列
            exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT), // 交换器
            key = {"red", "blue"} // 路由键
    ))
    public void listenDirectQueue1(String message) {
        System.out.println("消费者接收到direct.queue1消息:【" + message + "】");
    }
    
    // publisher
    @Test
    public void testSendDirectExchange() {
        // 交换机名称
        String exchangeName = "itcast.direct";
        // 消息
        String msg = "hello, blue";
        rabbitTemplate.convertAndSend(exchangeName, "blue", msg);
    } 

Topic Exchange 主题模式

通过 RoutingKey 进行主题通配符匹配,Exchange 与 Queue 的 BindingKey 若匹配上消息的 Routingkey,则可以接收。

#指代0或多个单词 *指代一个单词

	// consumer
	@RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue1"), // 监听队列
            exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC), // 交换器
            key = "china.#" // 路由键
    ))
    public void listenTopicQueue1(String message) {
        System.out.println("消费者接收到topic.queue1消息:【" + message + "】");
    }
    
    // publisher
    @Test
    public void testSendTopicExchange() {
        // 交换机名称
        String exchangeName = "itcast.topic";
        // 消息
        String msg = "不涉及他国领土问题!";
        rabbitTemplate.convertAndSend(exchangeName, "china.news", msg);
    }

消息转换器

使用 RabbitTemplate 发送消息时,消息体都是 Object 类型,默认采用的是 Serializable 序列化。使用 Serializable 序列化十分笨重,我们可以采用自定义的消息转换器。

	// 导入依赖
	<dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.11.2</version>
    </dependency>
    
    // Consumer 和 Publisher: 覆盖MessageConverter 。
	@Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }