RabbitMQ 中的六大工作模式介绍与使用

文章目录

简单队列(Simple Queue)模式

  • 结构:一个生产者对应一个消费者,生产者将消息发送到指定队列,消费者从该队列获取消息。
  • 工作流程:生产者创建一个消息并发送到 RabbitMQ 的队列中,消费者从这个队列里取出消息进行处理。
  • 应用场景:适用于任务处理逻辑简单,对消息处理速度要求不高,且消息处理顺序要求不严格的场景,比如简单的日志收集系统。

以下是简单队列(Simple Queue)模式的使用案例。

配置类定义

java 复制代码
package test;

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

@Configuration
public class SimpleQueueConfig {

    @Bean
    public DirectExchange simpleQueueExchange() {
        return ExchangeBuilder
                // 指定名称为simple-queue-exchange的交换机
                .directExchange("simple-queue-exchange")
                // 声明为持久化交换机
                .durable(true)
                .build();
    }

    @Bean
    public Queue simpleQueue() {
        return QueueBuilder
                // 指定一个名称为simple-queue的持久化队列
                .durable("simple-queue")
                .build();
    }

    @Bean
    public Binding simpleQueueToSimpleQueueExchangebinding() {
        // 绑定队列给交换机,根据队列名为路由键
        return BindingBuilder
                .bind(simpleQueue())
                .to(simpleQueueExchange())
                .withQueueName();
    }

}

消费者定义

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class SimpleQueueConsumer {

    @RabbitListener(queues = "simple-queue")
    public void consumer(String message) {
        log.info("消费者接收消息:{}", message);
    }

}

发送消息测试消费

java 复制代码
package test;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringBootApp {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringBootApp.class, args);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        rabbitTemplate.convertAndSend("simple-queue", "hello world!");
    }

}

控制台输出:

工作队列(Work Queues)模式

  • 结构:一个生产者对应多个消费者,多个消费者共同从一个队列中获取消息进行处理。
  • 工作流程:生产者将消息发送到队列,多个消费者从该队列中获取消息(默认采用轮询的负载均衡策略),一条消息只会被一个消费者处理。
  • 应用场景:适用于需要将大量任务分发给多个工作者并行处理的场景,例如订单处理、图片处理等。

以下是工作队列(Work Queues)模式的使用案例。

配置类定义

定义一个 DirectExchange 类型的交换机与两个队列,通过队列名作为 RoutingKey 进行绑定。

java 复制代码
package test;

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

@Configuration
public class WorkQueueConfig {

    @Bean
    public DirectExchange workQueueExchange(){
        return ExchangeBuilder
                .directExchange("work-queue-exchange")
                .durable(true)
                .build();
    }

    @Bean
    public Queue workQueue(){
        return QueueBuilder
                .durable("work-queue")
                .build();
    }

    @Bean
    public Binding workQueueToWorkQueueExchangeBinding(){
        return BindingBuilder
                .bind(workQueue())
                .to(workQueueExchange())
                .withQueueName();
    }

}

消费者定义

定义两个消费者,分别监听两个不同的队列。

消费者 1:

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class WorkQueueConsumer01 {

    @RabbitListener(queues = "work-queue")
    public void receive(String message){
        log.info("WorkQueueConsumer01 receive a message:{}",message);
    }

}

消费者 2:

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class WorkQueueConsumer02 {

    @RabbitListener(queues = "work-queue")
    public void receive(String message){
        log.info("WorkQueueConsumer02 receive a message:{}",message);
    }

}

发送消息测试消费

work-queue-exchange 交换机中发送 10 条消息。

java 复制代码
package test;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringBootApp {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringBootApp.class, args);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        for (int i = 0; i < 10; i++) {
            rabbitTemplate.convertAndSend("work-queue-exchange", "work-queue", "hello world!" + i);
        }
    }

}

控制台输出:

在上例结果,消费者 1 与消费者 2 对消息进行了轮询处理。

负载均衡调优

在工作队列模式下,多个消费者处理消息默认按照轮询的规则进行处理。但是如果其中某个消费者的处理能力比其他消费者更为强大,那么它实际上可以处理更多的消息,如果此时还是按照轮询规则来处理消息,这样是对该消费者能力的一种浪费。

对于上述情况,在 RabbitMQ 中支持调整消费者处理消息时的负载均衡策略,具体而言,即通过设置预取数量(prefetch count)参数,以实现更灵活的消息分发策略,即让处理能力更强大的消费者处理更多的消息。

在 Spring Boot 中,可以通过设置 spring.rabbitmq.listener.simple.prefetch 参数来控制消费者的预取数量:

yaml 复制代码
spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1

将预取数量设置为 1,则每个消费者在处理完当前消息并确认(ACK)之后才会接收下一条消息,从而避免了某个消费者负载过重的情况。

模拟当某个消费者处理能力较弱:

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class WorkQueueConsumer01 {

    @RabbitListener(queues = "work-queue")
    public void receive(String message) throws InterruptedException {
        // 模拟WorkQueueConsumer01处理能力较弱
        Thread.sleep(1);
        log.info("WorkQueueConsumer01 receive a message:{}",message);
    }

}

再次向交换机发送 10 条消息,控制台输出:

在上述结果中,由于 WorkQueueConsumer01 处理消息的能力更弱,所以更多的消息被分摊到了 WorkQueueConsumer02 处理。

发布/订阅(Publish/Subscribe)模式

  • 结构:包含一个生产者、一个交换机(Exchange)和多个队列,多个消费者分别监听不同的队列。
  • 工作流程:生产者将消息发送到交换机,交换机将消息广播到绑定到它的所有队列,每个绑定的队列对应的消费者都能收到消息。
  • 应用场景:适用于一个消息需要被多个不同的服务或模块接收和处理的场景,如系统的通知功能。

以下是发布/订阅(Publish/Subscribe)模式的使用案例。

配置类定义

定义一个 FanoutExchange 类型的交换机与三个队列,让三个队列绑定到该交换机。

FanoutExchange 交换机不需要指定 RoutingKey。因为 FanoutExchange 交换机会将消息统一发送给与其绑定的所有队列,指定 RoutingKey 实际上没有任何意义。

java 复制代码
package test;

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

@Configuration
public class PublishSubscribeConfig {

    @Bean
    public Queue publishSubscribeQueue1() {
        return new Queue("publish-subscribe-queue-1");
    }

    @Bean
    public Queue publishSubscribeQueue2() {
        return new Queue("publish-subscribe-queue-2");
    }

    @Bean
    public Queue publishSubscribeQueue3() {
        return new Queue("publish-subscribe-queue-3");
    }

    @Bean
    public FanoutExchange publishSubscribeFanoutExchange() {
        return new FanoutExchange("publish-subscribe-fanout-exchange");
    }

    @Bean
    public Binding publishSubscribeBinding1() {
        return BindingBuilder
                .bind(publishSubscribeQueue1())
                .to(publishSubscribeFanoutExchange());
    }

    @Bean
    public Binding publishSubscribeBinding2() {
        return BindingBuilder
                .bind(publishSubscribeQueue2())
                .to(publishSubscribeFanoutExchange());
    }

    @Bean
    public Binding publishSubscribeBinding3() {
        return BindingBuilder
                .bind(publishSubscribeQueue3())
                .to(publishSubscribeFanoutExchange());
    }

}

消费者定义

定义三个消费者,分别监听三个不同的队列。

第一个消费者:

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class PublishSubscribeConsumer01 {

    @RabbitListener(queues = "publish-subscribe-queue-1")
    public void receive(String message) {
        log.info("PublishSubscribeConsumer 01 receive a Message:{}",message);
    }

}

第二个消费者:

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class PublishSubscribeConsumer02 {

    @RabbitListener(queues = "publish-subscribe-queue-2")
    public void receive(String message) {
        log.info("PublishSubscribeConsumer 02 receive a message:{}",message);
    }

}

第三个消费者:

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class PublishSubscribeConsumer03 {

    @RabbitListener(queues = "publish-subscribe-queue-3")
    public void receive(String message) {
        log.info("PublishSubscribeConsumer 03 receive a message:{}",message);
    }

}

发送消息测试消费

publish-subscribe-fanout-exchange 发送一条消息。

java 复制代码
package test;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringBootApp {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringBootApp.class, args);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        rabbitTemplate.convertAndSend("publish-subscribe-fanout-exchange","","hello world!");
    }

}

控制台输出:

路由(Routing)模式

  • 结构:同样包含生产者、交换机和多个队列,但交换机类型为直连交换机(Direct Exchange),队列通过路由键(routing key)绑定到交换机。
  • 工作流程:生产者发送消息时指定路由键,交换机根据路由键将消息路由到与之绑定的队列,只有绑定了对应路由键的队列才能收到消息。
  • 应用场景:适用于根据不同的业务规则将消息路由到不同队列的场景,如根据日志的级别(info、error 等)将日志消息路由到不同的队列。

以下是路由(Routing)模式的使用案例。

配置类定义

定义一个 DirectExchange,因为 DirectExchange 可以通过 RoutingKey 精确匹配消息,当然也可以使用其他类型的交换机,如 ToppicExchange

定义三个不同名称的队列,其中两个队列绑定相同的 RoutingKey,而另外一个单独绑定一个队列。

java 复制代码
package test;

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

@Configuration
public class RoutingConfig {

    @Bean
    public DirectExchange routingExchange() {
        return ExchangeBuilder
                .directExchange("routing-exchange")
                .durable(true)
                .build();
    }

    @Bean
    public Queue routingQueue1() {
        return QueueBuilder
                .durable("routing-queue-1")
                .build();
    }

    @Bean
    public Queue routingQueue2() {
        return QueueBuilder
                .durable("routing-queue-2")
                .build();
    }

    @Bean
    public Queue routingQueue3() {
        return QueueBuilder
                .durable("routing-queue-3")
                .build();
    }

    @Bean
    public Binding routingQueue1ToRoutingExchangeBinding() {
        return BindingBuilder
                .bind(routingQueue1())
                .to(routingExchange())
                .with("routing-key-1");
    }

    @Bean
    public Binding routingQueue2ToRoutingExchangeBinding() {
        return BindingBuilder
                .bind(routingQueue2())
                .to(routingExchange())
                .with("routing-key-2");
    }

    @Bean
    public Binding routingQueue3ToRoutingExchangeBinding() {
        return BindingBuilder
                .bind(routingQueue3())
                .to(routingExchange())
                .with("routing-key-2");
    }

}

消费者定义

定义三个消费者,分别监听三个不同的队列。

第一个消费者:

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class RoutingQueueConsumer01 {

    @RabbitListener(queues = "routing-queue-1")
    public void receive(String message){
        log.info("RoutingQueueConsumer01 receive a message:{}",message);
    }

}

第二个消费者:

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class RoutingQueueConsumer02 {

    @RabbitListener(queues = "routing-queue-2")
    public void receive(String message){
        log.info("RoutingQueueConsumer02 receive a message:{}",message);
    }

}

第三个消费者:

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class RoutingQueueConsumer03 {

    @RabbitListener(queues = "routing-queue-3")
    public void receive(String message){
        log.info("RoutingQueueConsumer03 receive a message:{}",message);
    }

}

发送消费测试消费

向交换机发送消息,并指定 RoutingKey 为其中的两个。

java 复制代码
package test;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringBootApp {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringBootApp.class, args);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        rabbitTemplate.convertAndSend("routing-exchange", "routing-key-1", "hello world by routing-key-1!");
        rabbitTemplate.convertAndSend("routing-exchange", "routing-key-2", "hello world by routing-key-2!");
    }

}

控制台输出:

上述结果中,三个消费者都接受到了消息并成功处理。但实际上发送消息时,交换机只向两个 RoutingKey 发送消息,而另外一个 RoutingKey 并没有发送。这样的情况下,三个消息者监听三个不同队列仍然能够接受消息并处理,这是因为它们监听的队列所绑定的 RoutingKey 都被交换机投递了消息。

主题(Topics)模式

  • 结构:包含生产者、主题交换机(Topic Exchange)和多个队列,队列通过绑定键(binding key)绑定到交换机,绑定键可以使用通配符。
  • 工作流程:生产者发送消息时指定路由键,交换机根据绑定键和路由键的匹配规则将消息路由到相应的队列。
  • 应用场景:适用于需要根据消息的主题进行灵活路由的场景,如新闻分类订阅,用户可以根据不同的主题订阅不同的新闻。

生产者发送包含主题的消息,主题由一个或多个单词组成,单词之间使用点号 . 进行分隔。消费者可以使用通配符 *# 对主题进行模糊匹配:

  • *:匹配一个单词,可以代表任意一个词。
  • #:匹配零个或多个单词,可以代表一个或多个词。

以下是主题(Topics)模式的使用案例。

配置类定义

定义一个 TopicExchange,分别通过 log.info.*log.error.#RoutingKey 绑定到两个不同的队列。

java 复制代码
package test;

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

@Configuration
public class TopicConfig {

    @Bean
    public Queue topicQueue1() {
        return new Queue("topic-queue-1");
    }

    @Bean
    public Queue topicQueue2() {
        return new Queue("topic-queue-2");
    }

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

    @Bean
    public Binding bindingExchange() {
        return BindingBuilder.bind(topicQueue1()).to(topicExchange()).with("log.info.*");
    }

    @Bean
    public Binding bindingExchange2() {
        return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("log.error.#");
    }

}

消费者定义

定义两个消费者分别监听两个不同队列。

第一个消费者:

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class TopicQueueConsumer01 {

    @RabbitListener(queues = "topic-queue-1")
    public void receive(String message){
        log.info("TopicQueueConsumer01 receive a message:{}",message);
    }

}

第二个消费者:

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class TopicQueueConsumer02 {

    @RabbitListener(queues = "topic-queue-2")
    public void receive(String message){
        log.info("TopicQueueConsumer02 receive a message:{}",message);
    }

}

发送消息测试消费

topic-exchange 交换机发送六条消息,通过不同的 RoutingKey

java 复制代码
package test;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class SpringBootApp {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringBootApp.class, args);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        rabbitTemplate.convertAndSend("topic-exchange", "log.info.base", "log.info.base message!");
        rabbitTemplate.convertAndSend("topic-exchange", "log.info.customer", "log.info.customer message!");
        rabbitTemplate.convertAndSend("topic-exchange", "log.info.shop.goods", "log.info.shop.goods message!");
        rabbitTemplate.convertAndSend("topic-exchange", "log.error.base", "log.error.base message!");
        rabbitTemplate.convertAndSend("topic-exchange", "log.error.customer", "log.error.customer message!");
        rabbitTemplate.convertAndSend("topic-exchange", "log.error.shop.goods", "log.error.shop.goods message!");
    }

}

控制台输出:

RPC(Remote Procedure Call)模式

  • 结构:包含客户端(生产者)、服务端(消费者)、请求队列和响应队列。
  • 工作流程:客户端发送请求消息到请求队列,并设置一个回调队列用于接收响应;服务端从请求队列获取请求消息,处理后将响应消息发送到回调队列,客户端从回调队列获取响应。
  • 应用场景:适用于需要在分布式系统中进行远程过程调用的场景,如微服务之间的调用。

以下是主题(Topics)模式的使用案例。

配置类定义

定义一个交换机与一个队列,通过队列名进行绑定。

java 复制代码
package test;

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.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RpcConfig {

    @Bean
    public Queue rpcQueue() {
        return new Queue("rpc-queue", true);
    }

    @Bean
    public DirectExchange rpcExchange() {
        return new DirectExchange("rpc-exchange");
    }

    @Bean
    public Binding rpcQueueToRpcExchangeBinding() {
        return BindingBuilder.bind(rpcQueue()).to(rpcExchange()).withQueueName();
    }

    @Bean
    public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

}

请求与响应实体类

创建 RPC 请求与响应实体类。

java 复制代码
package test;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RpcRequest implements Serializable {

    private String message;

}
java 复制代码
package test;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RpcResponse implements Serializable {

    private int result;

}

消费者定义

定义一个消费者从队列中接收消息。

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class RpcServer {

    @RabbitListener(queues = "rpc-queue")
    public RpcResponse handleRpcRequest(RpcRequest request) {
        log.info("RPC接收的消息:{}", request);
        return new RpcResponse(0);
    }
    
}

发送消息测试消费

测试 RPC 消息的发送。

java 复制代码
package test;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
@Slf4j
public class SpringBootApp {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringBootApp.class, args);
        RabbitTemplate rabbitTemplate = context.getBean(RabbitTemplate.class);
        String exchange = "rpc-exchange";
        String routingKey = "rpc-queue";
        RpcRequest rpcRequest = new RpcRequest("Rpc message!");
        RpcResponse rpcResponse = (RpcResponse) rabbitTemplate.convertSendAndReceive(exchange, routingKey, rpcRequest);
        log.info("RPC返回的结果:{}", rpcResponse);
    }

}

控制台输出:

相关推荐
Code哈哈笑15 分钟前
【图书管理系统】环境介绍、设计数据库和表、配置文件、引入依赖
java·数据库·spring boot·后端·mybatis
星沁城19 分钟前
133. 克隆图
java·数据结构·算法·leetcode
燃星cro31 分钟前
关于MindVault项目测试报告
java·开发语言·测试工具·spring
LDM>W<1 小时前
Easy云盘总结篇-文件上传01
java·spring boot·学习·ffmpeg
-曾牛1 小时前
Spring AI聊天模型API:轻松构建智能聊天交互
java·人工智能·后端·spring·交互·api·springai
凌辰揽月1 小时前
实时在线状态
java·python·算法
理想奋斗中2 小时前
Maven安装配置以及Idea中的配置教程
java·maven
幽络源小助理2 小时前
SpringBoot校园失物招领平台源码开发实现
java·spring boot·后端
张张张3122 小时前
4.29-4.30 Maven+单元测试
java·单元测试·maven
代码匠心2 小时前
从零开始学Flink:开启实时计算的魔法之旅
java·大数据·flink