RabbitMq消息队列

1.MQ概述

1.1.初识MQ

分布式架构中微服务之间的通信主要有两种方式、同步和异步。Feign就是基于Http协议的同步通信方式,而MQ则是微服务异步调用的常用方案

对比Feign和MQ

feign采用同步调用方式,具有时效性强等优点,但是性能低、吞吐量下降、耦合度高容易导致级联失败等缺点

MQ采用异步调用方式,具有性能高、吞吐量高、解耦合、流量削峰、故障隔离等优点;但是也存在架构复杂、业务没有明显流程线、跟踪管理困难、强烈依靠Broker的可靠性等缺点

MQ使用事件驱动模式作为异步调用的常用实现:下面就是MQ调用流程图

在这里插入图片描述

1.2.MQ常用技术方案

方案RabbitMQActiveMQRocketMQkafka
公司RabbitApache阿里巴巴Apache
开发语言Erlangjavajavascala & java
可用性一般
单机吞吐量一般极高
消息延迟微秒级毫秒级毫秒级毫秒级
消息可靠性一般一般

2.RabbitMQ

2.1.RabbitMQ通信结构

在这里插入图片描述

2.2.简单消息发送demo

2.2.1.引入AMQP依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.2.2.配置rabbitmq
server:
  port: 7713
spring:
  rabbitmq:
    host: localhost # 服务器地址
    port: 5672 # 服务器端口
#    virtual-host: /  # 虚拟主机地址,每一个虚拟主机相当于一台独立的rabbitmq服务器,可以用来做环境隔离、业务拆分
    username: guest # 服务器用户名
    password: guest # 服务器密码
2.2.3.Producer消息生产者
package com.acx;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

@SpringBootTest
@RunWith(SpringRunner.class)
public class MqProducerTest {

    @Test
    public void pushMsg() {
        try {
            String queueName = "hello.word";
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("127.0.0.1");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("guest");
            connectionFactory.setPassword("guest");
            //1.创建rabbitmq连接
            Connection connection = connectionFactory.newConnection();
            //2.创建channel通道
            Channel channel = connection.createChannel();
            //3.channel绑定队列
            channel.queueDeclare(queueName, false, false, false, null);
            //4.发送消息
            String msg = "hello word!!!";
            channel.basicPublish("", queueName, null, msg.getBytes());
            System.out.println("publisher: mq消息发送成功!!!! msg=" + msg);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }

}
2.2.4.Consumer消息消费者
package com.acx;

import com.rabbitmq.client.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

@SpringBootTest
@RunWith(SpringRunner.class)
public class MqConsumerTest {

    @Test
    public void consumerMsg() {
        try {
            String queueName = "hello.word";
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("127.0.0.1");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("guest");
            connectionFactory.setPassword("guest");
            //1.创建rabbitmq连接
            Connection connection = connectionFactory.newConnection();
            //2.创建channel通道并绑定队列
            Channel channel = connection.createChannel();
            //3.channel绑定队列
            channel.queueDeclare(queueName, false, false, false, null);
            //4.接收消息
            channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope,
                                           AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String msg = new String(body);
                    System.out.println("消费了队列 ---- queue=" + queueName + " msg=" + msg);
                }
            });
            System.out.println("begin consumer mq msg");
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
    }

}

3.Spring AMQP

3.1.概述

AMQP:是用于在应用程序之间传递业务消息的开放标准。此协议与语言和平台无关,更符合微服务中独立性的要求。

Spring AMQP:是基于AMQP协议定义的一套API规范,提供了模板来发送和接受消息。主要包括两部分,其中Spring-AMQP是基础抽象,Spring-Rabbit是底层默认实现。

3.2.消息简单发送

3.2.1.配置queue
package com.acx.config;

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMqConfig {

    @Bean
    public Queue helloWordQueue() {
        return new Queue("helloWord");
    }

}
3.2.生产者发送消息
package com.acx.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/mq_test")
public class MqController {

    private static final Logger logger = LoggerFactory.getLogger(MqController.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/send_hello_word")
    public String sendHelloWord() {
        String msg = "hello word!!!";
        //使用RabbitTemplate发送mq消息
        rabbitTemplate.convertAndSend("helloWord", msg);
        logger.info("rabbit mq发送端发送消息OK. msg={}", msg);
        return msg;
    }

}
3.2.3.消费者消费消息
package com.acx.consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class MqConsumer {

    private static final Logger logger = LoggerFactory.getLogger(MqConsumer.class);

    @RabbitListener(queues = "helloWord")
    public void helloWordConsume(Message message) {
        String msg = new String(message.getBody());
        logger.info("rabbit mq 消费端获取消息OK. msg={}", msg);
    }

}

3.3.工作队列_WorkQueue

3.3.1.生产者发送50条消息
package com.acx.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/mq_test")
public class MqController {

    private static final Logger logger = LoggerFactory.getLogger(MqController.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/send_hello_word")
    public String sendHelloWord() {
        String msg = "hello word!!! index = ";
        for (int i = 1; i <= 50; i++) {
            rabbitTemplate.convertAndSend("helloWord", msg + i);
        }
        logger.info("rabbit mq发送端发送消息OK. msg={}", msg);
        return msg;
    }

}
3.3.2.消费者消费_两个消费端消费同一个队列
package com.acx.consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class MqConsumer {

    private static final Logger logger = LoggerFactory.getLogger(MqConsumer.class);

    @RabbitListener(queues = "helloWord")
    public void helloWordConsume1(Message message) throws InterruptedException {
        String msg = new String(message.getBody());
        logger.info("rabbit mq 01 消费端获取消息OK. msg={}", msg);
        //设置等待时间为20ms,这样这个消费端每秒可处理的就是50条消息
        Thread.sleep(20);
    }

    @RabbitListener(queues = "helloWord")
    public void helloWordConsume2(Message message) throws InterruptedException {
        String msg = new String(message.getBody());
        logger.error("rabbit mq 02 消费端获取消息OK. msg={}", msg);
        //设置等待时间为100ms,这样这个消费端每秒可处理的就是10条消息
        Thread.sleep(100);
    }

}

3.3.3.测试结果
  • 消费端1:拿到了25条消息,并且拿到的都是奇数索引的消息,一秒内全部消费完
  • 消费端2:拿到了25条消息,并且拿到的都是偶数索引的消息 ,用时了3秒才消费完
2022-05-23 21:58:46.516 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 2
2022-05-23 21:58:46.516  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 1
2022-05-23 21:58:46.546  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 3
2022-05-23 21:58:46.578  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 5
2022-05-23 21:58:46.609  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 7
2022-05-23 21:58:46.624 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 4
2022-05-23 21:58:46.641  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 9
2022-05-23 21:58:46.672  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 11
2022-05-23 21:58:46.704  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 13
2022-05-23 21:58:46.736  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 15
2022-05-23 21:58:46.736 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 6
2022-05-23 21:58:46.768  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 17
2022-05-23 21:58:46.799  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 19
2022-05-23 21:58:46.830  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 21
2022-05-23 21:58:46.846 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 8
2022-05-23 21:58:46.862  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 23
2022-05-23 21:58:46.893  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 25
2022-05-23 21:58:46.924  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 27
2022-05-23 21:58:46.955 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 10
2022-05-23 21:58:46.955  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 29
2022-05-23 21:58:46.987  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 31
2022-05-23 21:58:47.018  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 33
2022-05-23 21:58:47.049  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 35
2022-05-23 21:58:47.065 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 12
2022-05-23 21:58:47.081  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 37
2022-05-23 21:58:47.112  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 39
2022-05-23 21:58:47.144  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 41
2022-05-23 21:58:47.175 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 14
2022-05-23 21:58:47.175  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 43
2022-05-23 21:58:47.206  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 45
2022-05-23 21:58:47.238  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 47
2022-05-23 21:58:47.268  INFO 10788 --- [ntContainer#0-1] com.acx.consumer.MqConsumer              : rabbit mq 01 消费端获取消息OK. msg=hello word!!! index = 49
2022-05-23 21:58:47.283 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 16
2022-05-23 21:58:47.392 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 18
2022-05-23 21:58:47.500 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 20
2022-05-23 21:58:47.609 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 22
2022-05-23 21:58:47.719 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 24
2022-05-23 21:58:47.828 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 26
2022-05-23 21:58:47.939 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 28
2022-05-23 21:58:48.049 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 30
2022-05-23 21:58:48.159 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 32
2022-05-23 21:58:48.270 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 34
2022-05-23 21:58:48.377 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 36
2022-05-23 21:58:48.488 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 38
2022-05-23 21:58:48.596 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 40
2022-05-23 21:58:48.704 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 42
2022-05-23 21:58:48.814 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 44
2022-05-23 21:58:48.921 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 46
2022-05-23 21:58:49.031 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 48
2022-05-23 21:58:49.141 ERROR 10788 --- [ntContainer#1-1] com.acx.consumer.MqConsumer              : rabbit mq 02 消费端获取消息OK. msg=hello word!!! index = 50

3.3.4.消费预取机制

预取机制:上述发送给队列的50条消息,因为RabbitMq默认的预取机制是无限条数,所以RabbitMQ的预取机制都会提前轮询规则平均将消息分配给每个消费端,这样即使消费端1的消费能力更强,但是两个消费端最终消费的消息数量是一样的。但是消费端2用了3秒才消费完消息,这显然是导致了消费端2的消费阻塞

3.3.5.调整消费预取机制
  • 将其预取消息调整为1,这样消费端只有处理完一条消息才会去拿下一条消息,而不是无限制条数预取。
# 消费端配置
server:
  port: 7712
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    listener:
      simple:
        prefetch: 1 # 消息预取消息条数为1

4.SpringAMQP发布订阅模型

4.1.发布订阅模型

在这里插入图片描述

定义:发布订阅模式允许将同一消息发送给多个队列,通过exchange实现。

exchange类型

  • Fanout广播: 将收到的消息路由到每一个跟其绑定的queue

  • Direct路由:会将消息根据routing规则路由到指定的queue,因为被称为路由模式

  • Topic主题:会将消息根据Topic规则路由到指定的queue,Topic和routing的却别就是Topic必须是多个单词的组合并且以 . 分割开来,如school.hotel.family

exchange作用:负责消息路由到队列而不是消息存储,路由失败则消息丢失

4.2.FanoutExchange广播

4.2.1.配置交换机和队列绑定
package com.acx.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class FanoutMqConfig {

    @Bean
    public Queue fanoutQueue1() {
        return new Queue("fanoutQueue1");
    }

    @Bean
    public Queue fanoutQueue2() {
        return new Queue("fanoutQueue2");
    }

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("fanoutExchange");
    }

    @Bean
    public Binding bindingFanoutQueue1(@Qualifier("fanoutExchange") FanoutExchange exchange,@Qualifier("fanoutQueue1") Queue queue) {
        return BindingBuilder.bind(queue).to(exchange);
    }

    @Bean
    public Binding bindingFanoutQueue2(@Qualifier("fanoutExchange") FanoutExchange exchange,@Qualifier("fanoutQueue2") Queue queue) {
        return BindingBuilder.bind(queue).to(exchange);
    }
}

4.2.2.生产者发送消息
package com.acx.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/mq_test")
public class MqController {

    private static final Logger logger = LoggerFactory.getLogger(MqController.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/send_fanout_msg")
    public String sendFanoutMsg() {
        String msg = "广播exchange发送消息";
        rabbitTemplate.convertAndSend("fanoutExchange", "", msg);
        logger.info("rabbit mq发送广播消息成功. msg={}",msg);
        return msg;
    }
}

4.2.3.消费者消费消息
package com.acx.consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class FanoutMqConsumer {

    private static final Logger logger = LoggerFactory.getLogger(FanoutMqConsumer.class);

    @RabbitListener(queues = "fanoutQueue1")
    public void consumerFanoutQueue1(Message message) {
        String msg = new String(message.getBody());
        logger.info("消费端消费fanout_queue01队列消息成功. msg={}", msg);
    }

    @RabbitListener(queues = "fanoutQueue2")
    public void consumerFanoutQueue2(Message message) {
        String msg = new String(message.getBody());
        logger.info("消费端消费fanout_queue02队列消息成功. msg={}", msg);
    }

}

4.2.4.测试结果
  • 我们可以看到两个队列都有这个消息并被相应的消费端消费成功了
2022-05-24 21:21:11.729  INFO 3848 --- [ntContainer#0-1] com.acx.consumer.FanoutMqConsumer        : 消费端消费fanout_queue02队列消息成功. msg=广播exchange发送消息
2022-05-24 21:21:11.729  INFO 3848 --- [ntContainer#1-1] com.acx.consumer.FanoutMqConsumer        : 消费端消费fanout_queue01队列消息成功. msg=广播exchange发送消息

4.3.DirectExchange路由交换机

4.3.1.配置交换机和队列绑定
package com.acx.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DirectMqConfig {

    @Bean
    public Queue directQueue1(){
        return new Queue("directQueue1");
    }

    @Bean
    public Queue directQueue2(){
        return new Queue("directQueue2");
    }

    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange("directExchange");
    }

    @Bean
    public Binding bindingDirect1(@Qualifier("directExchange") DirectExchange exchange, @Qualifier("directQueue1") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("red");
    }

    @Bean
    public Binding bindingDirect2(@Qualifier("directExchange") DirectExchange exchange,@Qualifier("directQueue1") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("blue");
    }

    @Bean
    public Binding bindingDirect3(@Qualifier("directExchange") DirectExchange exchange,@Qualifier("directQueue2") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("yellow");
    }

    @Bean
    public Binding bindingDirect4(@Qualifier("directExchange") DirectExchange exchange,@Qualifier("directQueue2") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("blue");
    }

}

4.3.2.生产者生产消息
  • 如下:我们发送了三次消息,分别使用了不同的routing策略
package com.acx.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/mq_test")
public class MqController {

    private static final Logger logger = LoggerFactory.getLogger(MqController.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/send_direct_msg")
    public String sendDirectMsg() {
        String[] routingNames = new String[]{"red", "blue", "yellow"};
        for (String routName : routingNames) {
            String msg = "发送路由交换机消息。name=" + routName;
            rabbitTemplate.convertAndSend("directExchange", routName, msg);
        }
        return "发送路由消息成功";
    }

}

4.3.3.消费者消费消息
package com.acx.consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class DirectMqConsumer {

    private static final Logger logger = LoggerFactory.getLogger(DirectMqConsumer.class);

    @RabbitListener(queues = "#{directQueue1}")
    public void consumerDirectQueue01(Message message) {
        String msg = new String(message.getBody());
        logger.info("路由交换机消费端。消费directQueue01队列消息成功. msg={}", msg);
        logger.info("======================================================");
    }

    @RabbitListener(queues = "#{directQueue2}")
    public void consumerDirectQueue02(Message message) {
        String msg = new String(message.getBody());
        logger.info("路由交换机消费端。消费directQueue02队列消息成功. msg={}", msg);
        logger.info("======================================================");
    }

}

4.3.4.测试结果
  • 当按照blue路由规则发送消息时,两个队列都会接收到这个消息。而Red和Yellow则分别被队列1和队列2拿到并被消费端消费了。
2022-05-24 22:20:25.119  INFO 17832 --- [ntContainer#0-1] com.acx.consumer.DirectMqConsumer        : 路由交换机消费端。消费directQueue01队列消息成功. msg=发送路由交换机消息。name=red
2022-05-24 22:20:25.119  INFO 17832 --- [ntContainer#1-1] com.acx.consumer.DirectMqConsumer        : 路由交换机消费端。消费directQueue02队列消息成功. msg=发送路由交换机消息。name=blue
2022-05-24 22:20:25.119  INFO 17832 --- [ntContainer#0-1] com.acx.consumer.DirectMqConsumer        : ======================================================
2022-05-24 22:20:25.119  INFO 17832 --- [ntContainer#1-1] com.acx.consumer.DirectMqConsumer        : ======================================================
2022-05-24 22:20:25.120  INFO 17832 --- [ntContainer#0-1] com.acx.consumer.DirectMqConsumer        : 路由交换机消费端。消费directQueue01队列消息成功. msg=发送路由交换机消息。name=blue
2022-05-24 22:20:25.120  INFO 17832 --- [ntContainer#1-1] com.acx.consumer.DirectMqConsumer        : 路由交换机消费端。消费directQueue02队列消息成功. msg=发送路由交换机消息。name=yellow
2022-05-24 22:20:25.120  INFO 17832 --- [ntContainer#0-1] com.acx.consumer.DirectMqConsumer        : ======================================================
2022-05-24 22:20:25.120  INFO 17832 --- [ntContainer#1-1] com.acx.consumer.DirectMqConsumer        : ======================================================

4.4.TopicExchange主题交换机

4.4.1.配置交换机和队列绑定
  • #:通配符,代指0个或者多个单词
  • *:代指1个单词
package com.acx.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TopicMqConfig {

    @Bean
    public Queue topicQueue1(){
        return new Queue("topicQueue1");
    }

    @Bean
    public Queue topicQueue2(){
        return new Queue("topicQueue2");
    }

    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange("topicExchange");
    }

    @Bean
    public Binding bindingTopic1(@Qualifier("topicExchange") TopicExchange exchange,@Qualifier("topicQueue1") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("china.#");
    }

    @Bean
    public Binding bindingTopic2(@Qualifier("topicExchange") TopicExchange exchange,@Qualifier("topicQueue1") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("#.news");
    }

    @Bean
    public Binding bindingTopic3(@Qualifier("topicExchange") TopicExchange exchange,@Qualifier("topicQueue2") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("japan.#");
    }

    @Bean
    public Binding bindingTopic4(@Qualifier("topicExchange") TopicExchange exchange,@Qualifier("topicQueue2") Queue queue){
        return BindingBuilder.bind(queue).to(exchange).with("japan.news");
    }

}

4.4.2.生产者生产消息
package com.acx.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/mq_test")
public class MqController {

    private static final Logger logger = LoggerFactory.getLogger(MqController.class);

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @GetMapping("/send_topic_msg")
    public String sendTopicMsg(){
        String[] topicNames = new String[]{"chain.news","japan.weather","japan.news"};
        for (String topicName : topicNames) {
            String msg = "发送主题交换机消息。name=" + topicName;
            rabbitTemplate.convertAndSend("topicExchange", topicName, msg);
        }
        return "发送主题消息成功";
    }

}

4.4.3.消费者消费消息
package com.acx.consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class TopicMqConsumer {

    private static final Logger logger = LoggerFactory.getLogger(TopicMqConsumer.class);

    @RabbitListener(queues = "topicQueue1")
    public void consumerTopicQueue1(Message message) {
        String msg = new String(message.getBody());
        logger.info("消费topicQueue01消息成功. msg={}", msg);
        logger.info("======================");
    }

    @RabbitListener(queues = "topicQueue2")
    public void consumerTopicQueue2(Message message) {
        String msg = new String(message.getBody());
        logger.info("消费topicQueue02消息成功. msg={}", msg);
        logger.info("======================");
    }

}

4.4.4.测试结果
  • 每次发送消息都会寻找符合Topic匹配规则的队列进行投递,然后消费端消费对应的队列消息
2022-05-24 22:54:49.860  INFO 9184 --- [ntContainer#7-1] com.acx.consumer.TopicMqConsumer         : 消费topicQueue02消息成功. msg=发送主题交换机消息。name=japan.weather
2022-05-24 22:54:49.860  INFO 9184 --- [ntContainer#7-1] com.acx.consumer.TopicMqConsumer         : ======================
2022-05-24 22:54:49.860  INFO 9184 --- [ntContainer#6-1] com.acx.consumer.TopicMqConsumer         : 消费topicQueue01消息成功. msg=发送主题交换机消息。name=chain.news
2022-05-24 22:54:49.860  INFO 9184 --- [ntContainer#6-1] com.acx.consumer.TopicMqConsumer         : ======================
2022-05-24 22:54:49.861  INFO 9184 --- [ntContainer#7-1] com.acx.consumer.TopicMqConsumer         : 消费topicQueue02消息成功. msg=发送主题交换机消息。name=japan.news
2022-05-24 22:54:49.861  INFO 9184 --- [ntContainer#7-1] com.acx.consumer.TopicMqConsumer         : ======================
2022-05-24 22:54:49.861  INFO 9184 --- [ntContainer#6-1] com.acx.consumer.TopicMqConsumer         : 消费topicQueue01消息成功. msg=发送主题交换机消息。name=japan.news
2022-05-24 22:54:49.861  INFO 9184 --- [ntContainer#6-1] com.acx.consumer.TopicMqConsumer         : ======================

4.5.注解方式进行队列关系绑定

  • 以DirectExchange为例

  • 使用注解以后就不需要config配置类进行配置绑定了,可以完全代替前面的DirectMqConfig配置类的作用

package com.acx.consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class DirectMqConsumer {

    private static final Logger logger = LoggerFactory.getLogger(DirectMqConsumer.class);

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "directQueue1"),
            exchange = @Exchange(name = "directExchange", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void consumerDirectQueue01(Message message) {
        String msg = new String(message.getBody());
        logger.info("路由交换机消费端。消费directQueue01队列消息成功. msg={}", msg);
        logger.info("======================================================");
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "directQueue2"),
            exchange = @Exchange(name = "directExchange", type = ExchangeTypes.DIRECT),
            key = {"yellow", "blue"}
    ))
    public void consumerDirectQueue02(Message message) {
        String msg = new String(message.getBody());
        logger.info("路由交换机消费端。消费directQueue02队列消息成功. msg={}", msg);
        logger.info("======================================================");
    }

}

5.消息转换器

5.1.概述

说明:ConverterAndSend发送的msg是object传参,也就是说我们可以发送任意对象类型的消息。SpringAMQP会帮我们将这个Object对象序列化为字节以后发送,默认是根据SimpleMessageConverter类实现的序列化转化,而SimpleMessageConverter实际集成的是MessageConverter类,MessageConverter类默认使用的是JDK自带的序列化工具类。

JDK序列化缺点

  • 无法跨语言:只支持Java语言其他语言不支持
  • 序列化之后的码流太大
  • 序列化性能太低

5.2.切换为jackson序列化工具类

5.2.1.发送端切换Jackson序列化工具类
package com.acx.config;

import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory factory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(factory);
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
        return rabbitTemplate;
    }

}

5.2.2.消费端切换Jackson序列化工具类
package com.acx.config;

import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {

    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory factory) {
        RabbitTemplate rabbitTemplate = new RabbitTemplate(factory);
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
        return rabbitTemplate;
    }

}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值