【RabbitMQ】SpringBoot整合RabbitMQ:工作队列 && 发布/订阅模式 && 路由模式 && 通配符模式

文章目录


Spring 官方: Spring AMQP
RabbitMQ 官方: RabbitMQ tutorial - "Hello World!" | RabbitMQ

一、Work Queue(工作队列模式)

步骤:(后面其它模式也是如此)

  1. 引入依赖

  2. 编写 yml 配置文件,基本信息配置

  3. 编写生产者代码

  4. 编写消费者代码

    1. 定义监听类,使用 @RabbitListener 注解完成队列监听
  5. 运行观察结果

引入依赖

xml 复制代码
<!--Spring MVC相关依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<!--RabbitMQ相关依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

添加配置

yaml 复制代码
# 配置RabbitMQ的基本信息
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: liren
    password: 123123
    virtual-host: lirendada

编写生产者代码

常量类:

java 复制代码
public class Constants {
    public static final String WORK_QUEUE = "work_queue";
}

然后在 config 包中声明队列:(注意包要导对~)

java 复制代码
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {
    @Bean("workQueue")
    public Queue WorkQueue() {
        return QueueBuilder.durable(Constants.WORK_QUEUE).build();
    }
}

最后在需要发送消息的地方调用 RabbitTemplate 发送消息:

java 复制代码
@RequestMapping("/producer")
@RestController
public class ProducerController {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @RequestMapping("/work")
    public String work(){
        for (int i = 0; i < 10; i++) {
            // 使用内置交换机发送消息, routingKey和队列名称保持一致
            rabbitTemplate.convertAndSend("", Constants.WORK_QUEUE, "hello spring amqp: work...");
        }
        return "发送成功";
    }
}

编写消费者代码

定义监听类,用于消费队列中的消息:(注意包要导对~)

java 复制代码
import com.liren.springbootrabbitmq.constant.Constants;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class WorkListener {
    @RabbitListener(queues = Constants.WORK_QUEUE)
    public void workqueue1(Message message) {
        System.out.println("workqueue1 [" + Constants.WORK_QUEUE + "]收到消息:" + message);
    }

    @RabbitListener(queues = Constants.WORK_QUEUE)
    public void workqueue2(String message) {
        System.out.println("workqueue2 [" + Constants.WORK_QUEUE + "]收到消息:" + message);
    }
}

@RabbitListener 是 Spring 框架中用于监听 RabbitMQ 队列的注解,通过使用这个注解,可以定义一个方法,以便从 RabbitMQ 队列中接收消息。该注解支持多种参数类型,这些参数类型代表了从 RabbitMQ 接收到的消息和相关信息。

以下是一些常用的参数类型:

  1. String:返回消息的内容
  2. Message (org.springframework.amqp.core.Message):Spring AMQP 的Message类,返回原始的消息体以及消息的属性,如消息ID、内容、队列信息等。
  3. Channel (com.rabbitmq.client.Channel):RabbitMQ 的通道对象,可以用于进行更高级的操作,如手动确认消息。

运行结果

运行程序,然后发起请求,会有三个队列接收消息,如下所示:

管理页面中可以看到三个消费者以及一个生产者通道:

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

RabbitMQ 交换机常见三种类型:fanoutdirecttopic,不同类型有着不同的路由策略。

  1. Fanout广播策略 ,将消息交给所有绑定到该交换机的队列(Publish/Subscribe 模式
  2. Direct定向策略 ,把消息交给符合指定 routing key 的队列(Routing 模式
  3. Topic通配符策略 ,把消息交给符合 routing pattern 的队列(Topics 模式

编写生产者代码

常量类:

java 复制代码
// 发布订阅模式
public static final String FANOUT_QUEUE1 = "fanout.queue1";
public static final String FANOUT_QUEUE2 = "fanout.queue2";
public static final String FANOUT_EXCHANGE = "fanout.exchange";

然后在 config 包中声明队列:(注意包要导对~)

java 复制代码
// 发布订阅模式
@Bean("publishConfirmQueue1")
public Queue publishConfirmQueue1() {
    return QueueBuilder.durable(Constants.FANOUT_QUEUE1).build(); // 声明队列
}
@Bean("publishConfirmQueue2")
public Queue publishConfirmQueue2() {
    return QueueBuilder.durable(Constants.FANOUT_QUEUE2).build(); // 声明队列
}

@Bean("fanoutExchange")
public FanoutExchange fanoutExchange() {
    return ExchangeBuilder.fanoutExchange(Constants.FANOUT_EXCHANGE).build(); // 声明交换机
}

@Bean("fanoutBinding1")
public Binding fanoutBinding1(@Qualifier("publishConfirmQueue1") Queue queue,
                             @Qualifier("fanoutExchange") FanoutExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange); // 绑定交换机和队列
}
@Bean("fanoutBinding2")
public Binding fanoutBinding2(@Qualifier("publishConfirmQueue2") Queue queue,
                              @Qualifier("fanoutExchange") FanoutExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange); // 绑定交换机和队列
}

使用接口发送消息

java 复制代码
@RequestMapping("/fanout")
public String fanout() {
    rabbitTemplate.convertAndSend(Constants.FANOUT_EXCHANGE, "", "hello spring amqp: fanout...");
    return "发送成功!";
}

编写消费者代码

java 复制代码
@Component
public class FanoutListener {
    @RabbitListener(queues = Constants.FANOUT_QUEUE1)
    public void fanoutQueue1(String message) {
        System.out.println("fanoutQueue1 [" + Constants.FANOUT_QUEUE1 + "]收到消息:" + message);
    }

    @RabbitListener(queues = Constants.FANOUT_QUEUE2)
    public void fanoutQueue2(String message) {
        System.out.println("fanoutQueue2 [" + Constants.FANOUT_QUEUE2 + "]收到消息:" + message);
    }
}

消费者另一种写法

@RabbitListener 是一个功能强大的注解。这个注解里面可以配置 @QueueBinding@Queue@Exchange,直接通过这个组合注解一次性搞定多个交换机、绑定、路由、并且配置监听功能等

java 复制代码
@Slf4j
@Component
public class UserRegisterListener {
    @RabbitListener(
        bindings = @QueueBinding(
            value = @Queue(
                value = Constants.USER_QUEUE_NANE, // 队列名
                durable = "true"                   // 是否持久化
            ),
            exchange = @Exchange(
                value = Constants.USER_EXCHANGE_NAME, // 交换机名
                type = ExchangeTypes.FANOUT           // fanout 交换机
            )
            // fanout 不需要 routingKey
    )
)
    public void MailListenerQueue(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            // 处理用户注册消息
            String body = new String(message.getBody());
            log.info("用户注册消息处理成功,deliveryTag={}, message={}", deliveryTag, body);
            
            // 发送邮件TODO
            
            // 确认消息
            channel.basicAck(deliveryTag, true);
        }catch (Exception e) {
            // 异常拒绝消息,进行重发
            channel.basicNack(deliveryTag, true, true);
            log.error("用户注册消息处理失败,拒绝消息,deliveryTag={}", deliveryTag, e);
        }
    }
}

启动时 Spring AMQP 会做的事情,顺序大致是:

  1. QueueDeclare
    1. 声明一个 durable 队列
  2. ExchangeDeclare
    1. 声明一个 fanout 交换机
  3. QueueBind
    1. 把队列绑定到交换机

三、Routing(路由模式)

RabbitMQ 交换机常见三种类型:fanoutdirecttopic,不同类型有着不同的路由策略。

  1. Fanout广播策略 ,将消息交给所有绑定到该交换机的队列(Publish/Subscribe 模式
  2. Direct定向策略 ,把消息交给符合指定 routing key 的队列(Routing 模式
  3. Topic通配符策略 ,把消息交给符合 routing pattern 的队列(Topics 模式

路由模式采用的是 RabbitMQ 中的 Direct 定向策略,生产者发送消息的时候,交换机需要根据消息中的 Routing Key 将消息发送给指定的队列,而不是发给每一个队列了!

此时,队列和交换机的绑定,不能是任意的绑定了,而是要指定一个 Binding Key

只有队列绑定时的 Binding Key 和消息中的 Routing Key 完全一致,队列才会接收到消息

编写生产者代码

常量类:

java 复制代码
// 路由模式
public static final String DIRECT_EXCHANGE = "direct.exchange";
public static final String DIRECT_QUEUE1 = "direct.queue1";
public static final String DIRECT_QUEUE2 = "direct.queue2";

和发布订阅模式的区别是:交换机类型不同、绑定队列的 Binding Key 不同。

java 复制代码
// 路由模式(direct模式)
@Bean("directQueue1")
public Queue directQueue1() {
    return QueueBuilder.durable(Constants.DIRECT_QUEUE1).build(); // 声明队列
}
@Bean("directQueue2")
public Queue directQueue2() {
    return QueueBuilder.durable(Constants.DIRECT_QUEUE2).build(); // 声明队列
}

@Bean("directExchange")
public DirectExchange directExchange() {
    return ExchangeBuilder.directExchange(Constants.DIRECT_EXCHANGE).build(); // 声明交换机
}

// 队列1绑定orange
@Bean("directBinding1")
public Binding directBinding1(@Qualifier("directQueue1") Queue queue,
                              @Qualifier("directExchange") DirectExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange).with("orange"); // 绑定交换机和队列,以及bindingKey
}

// 队列2绑定green、black
@Bean("directBinding2")
public Binding directBinding2(@Qualifier("directQueue2") Queue queue,
                              @Qualifier("directExchange") DirectExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange).with("green"); // 绑定交换机和队列,以及bindingKey
}
@Bean("directBinding3")
public Binding directBinding3(@Qualifier("directQueue2") Queue queue,
                              @Qualifier("directExchange") DirectExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange).with("black"); // 绑定交换机和队列,以及bindingKey
}

使用接口发送消息:

java 复制代码
@RequestMapping("/direct/{routing_key}")
public String dirct(@PathVariable("routing_key") String routing_key) {
    rabbitTemplate.convertAndSend(Constants.DIRECT_EXCHANGE, routing_key, "hello spring amqp: direct..." + routing_key);
    return "发送成功!";
}

编写消费者代码

java 复制代码
@Component
public class DirectListener {
    @RabbitListener(queues = Constants.DIRECT_QUEUE1)
    public void directQueue1(String message) {
        System.out.println("directQueue1 [" + Constants.DIRECT_QUEUE1 + "]收到消息:" + message);
    }

    @RabbitListener(queues = Constants.DIRECT_QUEUE2)
    public void directQueue2(String message) {
        System.out.println("directQueue2 [" + Constants.DIRECT_QUEUE2 + "]收到消息:" + message);
    }
}

分别请求三个不同的 routingkey,结果如下所示:

四、Topics(通配符模式)

Topics 和 Routing 模式的区别是:

  1. 交换机类型不同: Topics 模式使用的交换机类型为 topic;Routing 模式使用的交换机类型为 direct
  2. 匹配规则不同: topic 类型的交换机在匹配规则上进行了扩展,Binding Key 支持通配符匹配;direct 类型的交换机路由规则是 Binding KeyRouting Key 完全匹配。

匹配规则有如下要求:

  1. Routing Key 是一系列由点 . 分隔的单词,比如 "stock.usd.nyse"、"nyse.vmw"、"quick.orange.rabbit"
  2. Binding KeyRouting Key 一样,也是点 . 分割的字符串
  3. Binding Key 中可以存在两种特殊字符串,用于模糊匹配
    1. \*:表示一个单词
    2. #:表示多个单词(0-N个)

编写生产者代码

常量类:

java 复制代码
// 通配符模式
public static final String TOPIC_EXCHANGE = "topic.exchange";
public static final String TOPIC_QUEUE1 = "topic.queue1";
public static final String TOPIC_QUEUE2 = "topic.queue2";

生产者代码如下所示:

java 复制代码
// 通配符模式(topics模式)
@Bean("topicQueue1")
public Queue topicQueue1() {
    return QueueBuilder.durable(Constants.TOPIC_QUEUE1).build(); // 声明队列
}
@Bean("topicQueue2")
public Queue topicQueue2() {
    return QueueBuilder.durable(Constants.TOPIC_QUEUE2).build(); // 声明队列
}

@Bean("topicExchange")
public TopicExchange topicExchange() {
    return ExchangeBuilder.topicExchange(Constants.TOPIC_EXCHANGE).build(); // 声明交换机
}

// 队列1绑定error, 仅接收error信息
@Bean("topicBinding1")
public Binding topicBinding1(@Qualifier("topicQueue1") Queue queue,
                              @Qualifier("topicExchange") TopicExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange).with("*.error"); // 绑定交换机和队列,以及bindingKey
}
// 队列2绑定info, error: error,info信息都接收
@Bean("topicBinding2")
public Binding topicBinding2(@Qualifier("topicQueue2") Queue queue,
                              @Qualifier("topicExchange") TopicExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange).with("*.error"); // 绑定交换机和队列,以及bindingKey
}
@Bean("topicBinding3")
public Binding topicBinding3(@Qualifier("topicQueue2") Queue queue,
                              @Qualifier("topicExchange") TopicExchange exchange) {
    return BindingBuilder.bind(queue).to(exchange).with("#.info"); // 绑定交换机和队列,以及bindingKey
}

使用接口发送消息:

java 复制代码
@RequestMapping("/topics/{routing_key}")
public String topics(@PathVariable("routing_key") String routing_key) {
    rabbitTemplate.convertAndSend(Constants.TOPIC_EXCHANGE, routing_key, "hello spring amqp: topics..." + routing_key);
    return "发送成功!";
}

编写消费者代码

java 复制代码
@Component
public class TopicListener {
    @RabbitListener(queues = Constants.TOPIC_QUEUE1)
    public void topicQueue1(String message) {
        System.out.println("topicQueue1 [" + Constants.TOPIC_QUEUE1 + "]收到消息:" + message);
    }

    @RabbitListener(queues = Constants.TOPIC_QUEUE2)
    public void topicQueue2(String message) {
        System.out.println("topicQueue2 [" + Constants.TOPIC_QUEUE2 + "]收到消息:" + message);
    }
}

分别请求两个不同的请求以及参数之后,运行结果如下:

相关推荐
蒸蒸yyyyzwd22 分钟前
cpp对象模型学习笔记1.1-2.8
java·笔记·学习
qq_297574671 小时前
【实战教程】SpringBoot 集成阿里云短信服务实现验证码发送
spring boot·后端·阿里云
程序员徐师兄1 小时前
Windows JDK11 下载安装教程,适合新手
java·windows·jdk11 下载安装·jdk11 下载教程
RANCE_atttackkk1 小时前
[Java]实现使用邮箱找回密码的功能
java·开发语言·前端·spring boot·intellij-idea·idea
五岳2 小时前
DTS按业务场景批量迁移阿里云MySQL表实战(下):迁移管理平台设计与实现
java·应用·dts
韩立学长2 小时前
【开题答辩实录分享】以《智能大学宿舍管理系统的设计与实现》为例进行选题答辩实录分享
数据库·spring boot·后端
zhougl9962 小时前
Java 所有关键字及规范分类
java·开发语言
Python 老手3 小时前
Python while 循环 极简核心讲解
java·python·算法
请叫我头头哥3 小时前
SpringBoot进阶教程(八十九)rabbitmq长链接及域名TTL,多机房切换配置重连能力
rabbitmq·springboot
java1234_小锋3 小时前
Java高频面试题:MyISAM索引与InnoDB索引的区别?
java·开发语言