使用SpringBoot整合RabbitMQ

785 阅读10分钟

1. 使用Direct交换机

1.1 前言

使用SpringBoot对RabbitMQ进行整合,模拟生产者服务器(9000)向向消费者服务器(8088)发送消息的过程,消息生产者通过接受Http请求向消息队列发送消息(Controller层、Service层),接收端则直接监听队列接收消息。这里Demo中通过请求两个不同的接口向不同的队列发送消息,在消费者端将会接收到对应监听队列的消息。

关于RabbitMQ的搭建及搭建中常见的问题参考连接:RabbitMQ搭建及问题

1.2 简介

Direct Exchange是RabbitMQ默认的交换机模式,也是最简单的模式,根据路由键全文匹配去寻找队列

Direct交换机

1.3 添加依赖

在pom.xml文件中添加依赖,主要是springboot中web和amqp的starter

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>

1.4 application.yml配置

消息生产者(发送端)配置,端口9000

server:
  port: 9000
spring:
  application:
    name: direct-sender
  rabbitmq:
    username: admin
    password: admin
    host: 192.168.108.128
    port: 5672

消费者(接收端)配置,端口8088

server:
  port: 8088
spring:
  application:
    name: direct-receiver
  rabbitmq:
    username: admin
    password: admin
    host: 192.168.108.128
    port: 5672

1.5 消息生产者(发送端)代码

配置SendConfig.java

配置中创建两个队列,分别为direct.queue.1和direct.queue.2,并且分别通过路由键direct.route.key.1和direct.route.key.2进行绑定。使用两个队列进行演示目的是展示交换机通过路由键将消息进行分发

@Configuration
public class SendConfig {
    public static final String DIRECT_QUEUE_1 = "direct.queue.1";
    public static final String DIRECT_QUEUE_2 = "direct.queue.2";
    public static final String DIRECT_EXCHANGE = "direct.exchange";
    public static final String DIRECT_ROUTE_KEY_1 = "direct.route.key.1";
    public static final String DIRECT_ROUTE_KEY_2 = "direct.route.key.2";

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

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

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

    @Bean
    public Binding binding1() {
        return BindingBuilder.bind(directQueue1()).to(directExchange()).with(DIRECT_ROUTE_KEY_1);
    }

    @Bean
    public Binding binding2() {
        return BindingBuilder.bind(directQueue2()).to(directExchange()).with(DIRECT_ROUTE_KEY_2);
    }

}

Controller层代码

设置访问路径和参数调用service层

@RestController
@RequestMapping("/direct")
public class SendController {

    @Autowired
    private DirectSendService directSendService;

    @GetMapping("/queue1/{msg}")
    public void sendQueue1(@PathVariable String msg) {
        directSendService.sendQueue1(msg);
    }

    @GetMapping("/queue2/{msg}")
    public void sendQueue2(@PathVariable String msg) {
        directSendService.sendQueue2(msg);
    }
}

Service层代码

使用RabbitTemplate的convertAndSend方法进行发送,方法有很多重载方法,选用convertAndSend(String exchange, String routingKey, Object object),指定交换机、路由键和发送的消息

@Service
public class DirectSendServiceImpl implements DirectSendService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void sendQueue1(String msg) {
        System.out.println("发送到队列1:" + msg);
        rabbitTemplate.convertAndSend(SendConfig.DIRECT_EXCHANGE, SendConfig.DIRECT_ROUTE_KEY_1, msg);
    }

    @Override
    public void sendQueue2(String msg) {
        System.out.println("发送到队列2:" + msg);
        rabbitTemplate.convertAndSend(SendConfig.DIRECT_EXCHANGE, SendConfig.DIRECT_ROUTE_KEY_2, msg);
    }
}

1.6 消费者(接收端)代码

消息接收端通过@RabbitListener注解监听队列,当队列有消息时自动读取

@Component
public class DirectReceiver {
    @RabbitListener(queues = "direct.queue.1")
    public void receiveDirect1(String msg) {
        System.out.println("接收到direct.queue.1的消息:" + msg);
    }

    @RabbitListener(queues = "direct.queue.2")
    public void receiveDirect2(String msg) {
        System.out.println("接收到direct.queue.2的消息:" + msg);
    }
}

1.7 验证运行结果

启动服务器,分别调用发送端接口

http://127.0.0.1:9000/direct/queue1/direct
http://127.0.0.1:9000/direct/queue2/direct

调用成功后RabbitMQ管理页面中便会新增两个队列,对列名为SendConfig中设置的名字

在这里插入图片描述

此时在消费者端控制台中会显示接收到的数据

在这里插入图片描述

此时如果关闭接收端服务器,然后一直调用发送接口,消息会都积累在队列中,在管理页面中显示为ready。

在这里插入图片描述

当再次启动接收端时便会以此将消息从队列中读取出来

2. 使用Fanout交换机

2.1 前言

使用SpringBoot对RabbitMQ进行整合,模拟生产者服务器(9000)向两台消费者服务器(8001和8002)发送消息的过程,消息生产者通过接受Http请求向消息队列发送消息(Controller层、Service层),接收端则直接监听队列接收消息。这里Demo中通过请求接口向绑定在交换机上的队列发送消息,在两个消费者服务器均会队列的消息。

关于RabbitMQ的搭建及搭建中常见的问题参考连接:RabbitMQ搭建及问题

2.2 简介

Fanout交换机会忽略路由键,将消息分发到所有绑定到交换机上的队列,即消息广播。 img

2.3 添加依赖

在pom.xml文件中添加依赖,主要是springboot中web和amqp的starter

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>

2.4 application.yml配置

demo模拟一个发送端,两个接收端的情况

消息生产者(发送端)配置,端口9001

server:
  port: 9001
spring:
  application:
    name: direct-sender
  rabbitmq:
    username: admin
    password: admin
    host: 192.168.108.128
    port: 5672

消费者1(接收端)配置,端口8001

server:
  port: 8001
spring:
  application:
    name: fanout-receiver-1
  rabbitmq:
    username: admin
    password: admin
    host: 192.168.108.128
    port: 5672

消费者2(接收端)配置,端口8002

server:
  port: 8002
spring:
  application:
    name: fanout-receiver-1
  rabbitmq:
    username: admin
    password: admin
    host: 192.168.108.128
    port: 5672

2.5 消息生产者(发送端)

SendConfig.java配置信息

配置信息中创建两个队列分别为fanout.queue.1、fanout.queue.2,通过FanoutExchange类创建Fanout交换机。由于Fanout交换机不关心路由键,因此在创建绑定时不用设置路由键。

@Configuration
public class SendConfig {
    public static final String FANOUT_QUEUE_1 = "fanout.queue.1";
    public static final String FANOUT_QUEUE_2 = "fanout.queue.2";
    public static final String FANOUT_EXCHANGE = "fanout.exchange";

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

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

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

    @Bean
    public Binding binding1() {
        return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange());
    }

    @Bean
    public Binding binding2() {
        return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange());
    }

}

Controller层

@RestController
@RequestMapping("/fanout")
class SendController{

    @Autowired
    private SendService sendService;

    @GetMapping("/send/{msg}")
    public void fanoutSend(@PathVariable String msg){
        sendService.send(msg);
    }
}

Service层

Service层中调用convertAndSend方法进行消息发送,由于Fanout不关心路由键,因此第二个参数可以随意设置,在这里选用空字符串

@Service
public class SendServiceImpl implements SendService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void send(String msg) {
        System.out.println("Fanout发送数据:" + msg);
        rabbitTemplate.convertAndSend(SendConfig.FANOUT_EXCHANGE, "", msg);
    }
}

2.6 消费者(接收端)

消费者1

@Component
public class FanoutReceiver {
    @Value("${server.port}")
    private String port;

    @RabbitListener(queues = "fanout.queue.1")
    public void receiveDirect1(String msg) {
        System.out.println(port+"收到fanout的消息:" + msg);
    }
}

消费者2

@Component
public class FanoutReceiver {
    @Value("${server.port}")
    private String port;

    @RabbitListener(queues = "fanout.queue.2")
    public void receiveDirect1(String msg) {
        System.out.println(port + "接收到fanout的消息:" + msg);
    }
}

在这里将配置文件中的端口号设置到变量中,已便于区分消息来源

2.7 验证结果

启动生产者和两个消费者服务器,调用接口发送消息

http://127.0.0.1:9001/fanout/send/fanout

在RabbitMQ管理界面中可以看到两个队列已经成功声明 在这里插入图片描述 此时,调用一次接口,在两个接收端均可以收到接口发送的消息

在这里插入图片描述

在这里插入图片描述

3. 使用Topic交换机

3.1 前言

使用SpringBoot对RabbitMQ进行整合,模拟生产者服务器(9002)向消费者服务器(8003)发送消息的过程,消息生产者通过接收 HTTP 请求向消息队列发送消息(Controller层、Service层),接收端则直接监听队列接收消息。这里的Demo分别设置有三个接口,每个接口向不同绑定路由规则的队列发送消息,观察消费者端接收情况。

关于RabbitMQ的搭建及搭建中常见的问题参考连接:RabbitMQ搭建及问题

3.2 简介

Topic交换机与Direct相似,通过与交换机绑定队列的路由键进行消息分发。不同的是Topic可以通过使用通配符( * 和 #)将消息分发到一个或者多个队列当中

通配符说明示例
*匹配一个或多个内容bigdata. * 可以匹配到 bigdata.spark 或者 bigdata.hadoop.hive 等
#匹配一个内容bigdata.# 只能匹配到 bigdata.spark 或者 bigdata.hadoop

img

3.3 添加依赖

在 pom.xml 文件中添加依赖,主要是 SpringBoot中 WEB 依赖 starter 和 amqp 的依赖 starter

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>

3.4 application.yml 配置

消息生产者(发送端)配置,端口9002

server:
  port: 9002
spring:
  application:
    name: topic-sender
  rabbitmq:
    username: admin
    password: admin
    host: 192.168.108.128
    port: 5672

消费者(接收端)配置,端口8003

server:
  port: 8003
spring:
  application:
    name: topic-receiver
  rabbitmq:
    username: admin
    password: admin
    host: 192.168.108.128
    port: 5672

3.5 消息生产者(发送端)

SendConfig.java配置信息

在配置信息中创建三个队列分别为topic.queue.1、topic.queue.2、topic.queue.3,并且绑定到交换机上的路由键分别为topic.message.rabbit、topic.message.*、topic.#

@Component
public class SendConfig {
    public static final String TOPIC_QUEUE_1 = "topic.queue.1";
    public static final String TOPIC_QUEUE_2 = "topic.queue.2";
    public static final String TOPIC_QUEUE_3 = "topic.queue.3";
    public static final String TOPIC_EXCHANGE = "topic.exchange";
    public static final String TOPIC_ROUTING_KEY_1 = "topic.message.rabbit";
    public static final String TOPIC_ROUTING_KEY_2 = "topic.message.*";
    public static final String TOPIC_ROUTING_KEY_3 = "topic.#";

    @Bean
    public Queue queue1() {
        return new Queue(TOPIC_QUEUE_1);
    }

    @Bean
    public Queue queue2() {
        return new Queue(TOPIC_QUEUE_2);
    }

    @Bean
    public Queue queue3() {
        return new Queue(TOPIC_QUEUE_3);
    }

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

    @Bean
    public Binding binding1() {
        return BindingBuilder.bind(queue1()).to(topicExchange()).with(TOPIC_ROUTING_KEY_1);
    }

    @Bean
    public Binding binding2() {
        return BindingBuilder.bind(queue2()).to(topicExchange()).with(TOPIC_ROUTING_KEY_2);
    }

    @Bean
    public Binding binding3() {
        return BindingBuilder.bind(queue3()).to(topicExchange()).with(TOPIC_ROUTING_KEY_3);
    }

}

Controller层

demo中有三个接口发送消息,分别发送消息到三个不同的路由键上,用于观察哪些接收端接收到了对应消息

@RestController
@RequestMapping("/topic")
public class SendController {
    @Autowired
    private TopicService topicService;

    @GetMapping("/send1/{msg}")
    public void send1(@PathVariable String msg) {
        topicService.send1(msg);
    }

    @GetMapping("/send2/{msg}")
    public void send2(@PathVariable String msg) {
        topicService.send2(msg);
    }

    @GetMapping("/send3/{msg}")
    public void send3(@PathVariable String msg) {
        topicService.send3(msg);
    }
}

Service层

  • send1方法发送信息到路由键topic.message.rabbit
  • send2方法发送信息到路由键topic.message.kafka
  • send3方法发送信息到路由键topic
@Service
public class TopicServiceImpl implements TopicService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void send1(String msg) {
        System.out.println("路由键 topic.message.rabbit 发送消息:"+msg);
        rabbitTemplate.convertAndSend(SendConfig.TOPIC_EXCHANGE, "topic.message.rabbit", msg);
    }

    @Override
    public void send2(String msg) {
        System.out.println("路由键 topic.message.kafka 发送消息:"+msg);
        rabbitTemplate.convertAndSend(SendConfig.TOPIC_EXCHANGE, "topic.message.kafka", msg);
    }

    @Override
    public void send3(String msg) {
        System.out.println("路由键 topic 发送消息:"+msg);
        rabbitTemplate.convertAndSend(SendConfig.TOPIC_EXCHANGE, "topic", msg);
    }
}

3.6 消费者(接收端)

@Component
public class TopicReceive {

    @RabbitListener(queues = "topic.queue.1")
    public void receiveTopic1(String msg) {
        System.out.println("topic.queue.1 接收到数据:" + msg);
    }

    @RabbitListener(queues = "topic.queue.2")
    public void receiveTopic2(String msg) {
        System.out.println("topic.queue.2 接收到数据:" + msg);
    }

    @RabbitListener(queues = "topic.queue.3")
    public void receiveTopic3(String msg) {
        System.out.println("topic.queue.3 接收到数据:" + msg);
    }
}

3.7 测试运行结果

1.调用接口send1,发送到路由键topic.message.rabbit

http://127.0.0.1:9002/topic/send1/topic

控制台信息: 在这里插入图片描述 可以看到三个队列均接收到了信息 2.调用接口send2,发送到路由键topic.message.kafka

http://127.0.0.1:9002/topic/send2/topic

控制台信息: 在这里插入图片描述

只有队列2和队列3接收到数据 3.调用接口send3,发送到路由键topic

http://127.0.0.1:9002/topic/send3/topic

控制台信息: 在这里插入图片描述 只有队列1接收到信息

4. 使用Headers交换机

4.1 前言

使用SpringBoot对RabbitMQ进行整合,模拟生产者服务器(9003)向消费者服务器(8004)发送消息的过程,消息生产者通过接受Http请求向消息队列发送消息(Controller层、Service层),接收端则直接监听队列接收消息。这里的Demo分别设置两种不同的绑定类型,观察消费者端接收消息的情况。

关于RabbitMQ的搭建及搭建中常见的问题参考连接:RabbitMQ搭建及问题

4.2 简介

头交换机(headers exchange)使用多个消息属性来代替路由键建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。头交换机可以视为直连交换机的另一种表现形式。头交换机能够像直连交换机一样工作,不同之处在于头交换机的路由规则是建立在头属性值之上,而不是路由键。路由键必须是一个字符串,而头属性值则没有这个约束,它们甚至可以是整数或者哈希值(字典)等

4.3 添加依赖

在pom.xml文件中添加依赖,主要是springboot中web和amqp的starter

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>

4.4 application.yml配置

消息生产者(发送端),端口9003

server:
  port: 9003
spring:
  application:
    name: header-sender
  rabbitmq:
    username: admin
    password: admin
    host: 192.168.108.128
    port: 5672

消费者(接收端),端口8004

server:
  port: 8004
spring:
  application:
    name: header-receiver-1
  rabbitmq:
    username: admin
    password: admin
    host: 192.168.108.128
    port: 5672

4.5 消息生产者(发送端)

SendConfig.java配置信息

配置信息中创建两个队列分别为header.queue.1和header.queue.2,在绑定的时候调用方法whereAll和whereAny,whereAll用于匹配所有头信息,whereAny只需要匹配到一条即可。

@Component
public class SendConfig {
    public static final String HEADER_QUEUE_1 = "header.queue.1";
    public static final String HEADER_QUEUE_2 = "header.queue.2";
    public static final String HEADER_EXCHANGE = "header.exchange";

    @Bean
    public Queue queue1() {
        return new Queue(HEADER_QUEUE_1);
    }

    @Bean
    public Queue queue2() {
        return new Queue(HEADER_QUEUE_2);
    }

    @Bean
    public HeadersExchange headersExchange() {
        return new HeadersExchange(HEADER_EXCHANGE);
    }

    @Bean
    public Binding binding1() {
        HashMap<String, Object> header = new HashMap<>();
        header.put("queue", "queue1");
        header.put("bindType", "whereAll");
        return BindingBuilder.bind(queue1()).to(headersExchange()).whereAll(header).match();
    }

    @Bean
    public Binding binding2() {
        HashMap<String, Object> header = new HashMap<>();
        header.put("queue", "queue2");
        header.put("bindType", "whereAny");
        return BindingBuilder.bind(queue2()).to(headersExchange()).whereAny(header).match();
    }
}

Controller层

@RestController
@RequestMapping("/header")
public class SendController {
    @Autowired
    private HeaderService headerService;

    @GetMapping("/send1/{msg}")
    public void send1(@PathVariable String msg) {
        headerService.send1(msg);
    }

    @GetMapping("/send2/{msg}")
    public void send2(@PathVariable String msg) {
        headerService.send2(msg);
    }
}

Service层

与前面不同的是,这里通过使用MessageProperties对象封装头信息,通过Message对象传递消息

@Service
public class HeaderServiceImpl implements HeaderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public void send1(String msg) {
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setHeader("queue", "queue1");
        messageProperties.setHeader("bindType", "whereAll");
        Message message = new Message(msg.getBytes(), messageProperties);
        System.out.println("发送消息:"+msg);
        rabbitTemplate.convertAndSend(SendConfig.HEADER_EXCHANGE, null, message);
    }

    @Override
    public void send2(String msg) {
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setHeader("queue", "queue2");
        messageProperties.setHeader("bindType", "whereAny");
        Message message = new Message(msg.getBytes(), messageProperties);
        System.out.println("发送消息:"+msg);
        rabbitTemplate.convertAndSend(SendConfig.HEADER_EXCHANGE, null, message);

    }
}

4.6 消费者(接收端)

@Component
public class receive {
    @RabbitListener(queues = "header.queue.1")
    public void receive1(String msg) {
        System.out.println("接收到 header.queue.1 发送的消息:" + msg);
    }

    @RabbitListener(queues = "header.queue.2")
    public void receive2(String msg) {
        System.out.println("接收到 header.queue.2 发送的消息:" + msg);
    }
}

5. 代码地址

RabbitDemo代码