SpringBoot集成RabbitMQ(第二节)

本文介绍了如何在SpringBoot项目中集成和使用RabbitMQ,包括添加依赖、配置MQ连接、创建用户对象,以及详细讲解了点对点模式、工作模式、订阅发布者模式、路由模式和主题模式这五种消息处理模式。每种模式下都包含了生产者和消费者的代码示例,以及结果验证和模式分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开始语

一位普通的程序员,慢慢在努力变强!

>>>>>>>RabbitMQ搭建方式👈

开始学习rabbitmq的猿友想必已经对java的springboot、cloud等框架模式已经了解、使用比较成熟了,这里就省略创建springboot项目的步骤了!

1.pom添加依赖

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

<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
</dependency>

<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>

2.yml配置mq

spring:
  application:
    name: springboot-rabbitmq
  # 配置rabbit连接 (填写你自己的mq地址:最好本机IP,而不是localhost)
  rabbitmq:
    host: tianyu.com.cn
    port: 5672
    username: guest
    password: guest
    # 环境隔离,默认使用“/”(虚拟主机)
    virtual-host: /
    connection-timeout: 2000000

3.mq模式的了解和学习

官网学习指引👈

3.0 User对象创建

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

import java.io.Serializable;

/**
 * @author 猿仁
 * @date 2023-01-31 09:38
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    private String name;

}

3.1 基础普通模式(模式一)

在这里插入图片描述

简介:
从上图可以看出,这个模式比较单一,一进一出,P是生产者,中间红色部分是消息缓冲区域(通道),C是消费者。
生产者也就是我们的发送者,将消息发送到消息缓冲区中,等待消费者接收消费。

3.1.1 生产者

温馨提示:需要到测试单元类中进行测试

@Autowired
private RabbitTemplate rabbitTemplate;
/**
 * 第一种模型:点对点
 */
@Test
public void helloWordTest() {
    // 字符串
    rabbitTemplate.convertAndSend("hello","字符串模型!");
    // 对象
    rabbitTemplate.convertAndSend("hello",new User("对象模型"));
}
3.1.2 消费者
import com.net.entity.User;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;

/**
 * 消费者监听 (第一种模型:点对点)
 * 默认创建的是:持久化队列
 *
 * @author 猿仁
 * @Queue(value = "队列名",durable = "是否持久化(默认持久【true】针对宕机等情况)",autoDelete = "是否自动删除(默认不删除【false】)")
 * @data 2023-01-31 09:38
 */
@Component
@Slf4j
@RabbitListener(queuesToDeclare = @Queue(value = "hello", durable = "false", autoDelete = "false"))
public class HelloCustomer {

    /**
     * 消费者对象模型1
     *
     * @param data Body响应内容
     * @param headers 请求头
     * @param channel 通道
     * @param message 消息
     * @return void
     * @author 猿仁
     * @date 2023/1/31 9:38
     */
    @RabbitHandler
    public void helloWord1(@Payload User data, @Headers Map<String, Object> headers, Channel channel, Message message) throws IOException, NoSuchMethodException {
        log.info("\nhelloWord1获取到了通道[hello]的数据!");
        log.info("Payload:  {}" , data);
        log.info("Headers:  {}" , headers);
        log.info("Channel:  {}" , channel);
        log.info("Message:  {}" , message);
    }

    /**
     * 消费者字符串模型2
     *
     * @param data Body响应内容
     * @param headers 请求头
     * @param channel 通道
     * @param message 消息
     * @return void
     * @author 猿仁
     * @date 2023/1/31 9:38
     */
    @RabbitHandler
    public void helloWord2(@Payload String data, @Headers Map<String, Object> headers, Channel channel, Message message) throws IOException, NoSuchMethodException {
        log.info("\nhelloWord2获取到了通道[hello]的数据!");
        log.info("Payload:  {}" , data);
        log.info("Headers:  {}" , headers);
        log.info("Channel:  {}" , channel);
        log.info("Message:  {}" , message);
    }

}
3.1.3 结果验证

在这里插入图片描述

3.2 工作模式(模式二)

在这里插入图片描述

简介:
从上图可以看出,此模式为一对多,P是生产者,中间红色部分是消息缓冲区域(通道),C1、C2是消费者。
生产者也就是我们的发送者,将消息发送到消息缓冲区中,等待消费者接收消费。
这个和3.1模式不同的点就是多了一个消费者,大大的增强了消费能力, 当消息发送频率很高,导致队列中的消息累积、阻塞,增加消费者来实现多消费模式是一种简单的解决方案!

3.2.1 生产者
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private WorkCustomer workCustomer;

/**
 * 第二种模型:一对多
 */
@Test
public void workTest() throws InterruptedException {
    for (int i = 1; i <= 90; i++) {
        rabbitTemplate.convertAndSend("work", "send 第" + i + "条消息!");
    }
    Thread.sleep(100000);
    System.out.println("work1Count = "+workCustomer.work1Count);
    System.out.println("work2Count = "+workCustomer.work2Count);
    System.out.println("work3Count = "+workCustomer.work3Count);
}
3.2.2 消费者
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;


/**
 * 消费者监听 (第二种模型:一对多)
 *
 * @author 猿仁
 * @data 2023-01-31 09:38
 */
@Component
@Slf4j
public class WorkCustomer {
    // 记录消费者消费的数量
    public int work1Count = 0;
    public int work2Count = 0;
    public int work3Count = 0;

    /**
     * 消费者1
     *
     * @param data Body响应内容
     * @return void
     * @author 猿仁
     * @date 2023/1/31 9:38
     */
    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void work1(String data) {
        log.info("\nwork1获取到了通道[work]的数据!{}", data);
        log.info("Payload:  {}", data);
        ++work1Count;
    }

    /**
     * 消费者2
     *
     * @param data Body响应内容
     * @return void
     * @author 猿仁
     * @date 2023/1/31 9:38
     */
    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void work2(String data) {
        log.info("\nwork2获取到了通道[work]的数据!{}", data);
        log.info("Payload:  {}", data);
        ++work2Count;
    }

    /**
     * 消费者2
     *
     * @param data Body响应内容
     * @return void
     * @author 猿仁
     * @date 2023/1/31 9:38
     */
    @RabbitListener(queuesToDeclare = @Queue("work"))
    public void work3(String data) throws InterruptedException {
        log.info("\nwork3获取到了通道[work]的数据!{}", data);
        log.info("Payload:  {}", data);
        // 在现实中,可能消费水平、能力都不一样,有的快有的慢,这么模拟一个慢的,睡2秒
        Thread.sleep(2000);
        ++work3Count;
    }
}
3.2.3 结果验证

在这里插入图片描述

3.2.4 结果分析

在这里插入图片描述

官网的描述:默认是轮训,每个消费者得到的数量是一致的,消费慢的也会接收消息,只是在等待被指定的消费者慢慢消费!
如果出现3个消费者,只有2个消息的时候,这个时候就是轮训给,给到谁谁就消费,和消费者的消费水平无关
在这里插入图片描述

3.2.5 限流模式 prefetch
# application.yml配置,生产者、消费者都不动,添加完监听者配置直接运行查看结果
spring:
  application:
    name: springboot-rabbitmq
  # 配置rabbit连接 (填写你自己的mq地址:最好本机IP,而不是localhost)
  rabbitmq:
    host: tianyu.com.cn
    port: 5672
    username: guest
    password: guest
    # 环境隔离,默认使用“/”(虚拟主机)
    virtual-host: /
    connection-timeout: 2000000
    # 开启监听者配置
    listener:
      simple:
         # 默认是轮训,平均分配,如果配置此选项,那么将有一个消费者达到指定的条数2,将不会再接收消息了。限流。也可以在监听者类中配置
         prefetch: 2

在这里插入图片描述

3.3 交换机模式理解

交换机种类如下(4种):

直联交换机(direct):Direct是RabbitMQ默认的交换机模式,先匹配, 再投送。创建消息队列的时候需要绑定一个key,可以理解为是一个路由key,根据交换机所绑定的路由key指向的对应的队列中(消息存放)
在这里插入图片描述
主题交换机(topic):这个可以理解为是direct的升级版,在Routing key上做了类似于正则表达式的路由规则
*号: 代表一个单词
#号: 代表零个或者多个单词

标头交换机(headers ):类似于在交换机的Headers头部配置一个k,v这样的键值对,消息将根据键值对来路由到对应的队列中,通过获取消息头部来进行完全匹配。(显示开发中基本不用)
在这里插入图片描述
扇出交换机(fanout):转发消息是最快的,以广播的形式将消息发送到绑定的队列上,没有key的概念。
在这里插入图片描述

温馨提示:多消费者,就是多加几个队列! 除了fanout模式,其他三种模式多几个消费者符合路由规则的消费者都会接收到消息,而不是像fanout一个消息只由一个消费者消费

3.4 订阅发布者模式(模式三)

在这里插入图片描述

简介:
从上图可以看出,这个模式比上2个复杂了一点,P生产者,X为交换机,红色部分为队列,C1,C2为消费者.
生产者先绑定交换机,然后交换机bing绑定了队列,将消息发送到指定的队列中,然后有由不同的消费者对应队列进行消费(消息分发)

3.4.1 生产者
@Autowired
private RabbitTemplate rabbitTemplate;

/**
 * 第三种模型:广播模式:fanout(消息分发,每个绑定交换机的队列都会收到消息)
 */
@Test
public void fanoutTest() {
    for (int i = 1; i <= 3; i++) {
        rabbitTemplate.convertAndSend("logs", "", "fanout send 【模型三】 " + i);
    }
}
3.4.2 消费者
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * 第三种模型
 */
@Component
@Slf4j
public class FanOutCustomer {

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//绑定临时队列
                    exchange = @Exchange(value = "logs", type = ExchangeTypes.FANOUT) //绑定交换机
            )
    })
    public void fanout1(String data, Channel channel, Message message) throws IOException {
        log.info("【fanout1 execute consumption:】" + data + "消费成功!");

    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//绑定临时队列,可以给名称,那么就不是临时队列
                    exchange = @Exchange(value = "logs", type = ExchangeTypes.FANOUT) //绑定交换机
            )
    })
    public void fanout2(String data, Channel channel, Message message) throws IOException {
        log.info("【fanout2 execute consumption:】" + data + "消费成功!");
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue(name = ExchangeTypes.FANOUT + "_consumer3", durable = "true"), //绑定队列,可以给名称,那么就不是临时队列
                    exchange = @Exchange(value = "logs", type = ExchangeTypes.FANOUT) //绑定交换机
            )
    })
    public void fanout3(String data) throws IOException {
        log.info("【fanout3 execute consumption:】" + data + "消费成功!");
    }
}
3.4.3 结果验证

在这里插入图片描述

3.5 路由模式(模式四)

在这里插入图片描述

简介:
从上图可以看出,P是生产者,X是交换机,中间红色部分是消息缓冲区域(通道),C1、C2是消费者。
可以看出这个模式和订阅模式比较像,唯一的区别是存在一个路由规则,可以理解为交换机和队列之间是存在一个 【标识】进行连接,如果标识对不上,那么队列将接收不到消息。

3.5.1 生产者
@Autowired
private RouteCustomer routeCustomer;

/**
 * 第四种模式:路由模式
 */
@Test
public void routeTest() throws InterruptedException {
    User user = new User("info你好!");
    rabbitTemplate.convertAndSend("route_direct", "info", user);
    // error消费出现异常,会造成死循环,
    // 解决次问题:1.直接吃掉异常,不要抛出,记录日志或者存库,2.开启消费者重试模式,限定次数
    user = new User("error你好!");
    rabbitTemplate.convertAndSend("route_direct", "error", user);
    user = new User("json你好!");
    rabbitTemplate.convertAndSend("route_direct", "json", user);
    user = new User("debug你好!");
    rabbitTemplate.convertAndSend("route_direct", "debug", user);
    user = new User("warn你好!");
    rabbitTemplate.convertAndSend("route_direct", "warn", user);
    Thread.sleep(16000);
    System.out.println("一共执行次数:"+routeCustomer.errorCount);
    System.out.println("执行次数明细:"+routeCustomer.errorCountDetail);
}
3.5.2 消费者
import com.net.entity.User;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 第四种模型:路由模式
 */
@Component
@Slf4j
public class RouteCustomer {

    private static final String INFO = "info";
    private static final String DEBUG = "debug";
    private static final String ERROR = "error";
    private static final String JSON = "json";
    public Map<Integer, String> errorCountDetail = new HashMap<>();
    public int errorCount = 0;

    @RabbitListener(bindings = {@QueueBinding(value = @Queue(name = RouteCustomer.ERROR + "_queue"),//绑定临时队列,可以给名称,那么就不是临时队列
            exchange = @Exchange(value = "route_direct", type = ExchangeTypes.DIRECT),//绑定交换机名称和类型(默认是direct类类型)
            key = RouteCustomer.ERROR)})
    public void error(User user, Channel channel, Message message, @Headers Map<String, Object> headers) {
        log.info("交换机模式【direct】,key【error】,开始消费:{},Channel:{}, Message:{}, Headers :{}", user, channel, message, headers);
        //try {
        //    int a = 0 / 0;
        //}catch (Exception e) {
        //    System.out.println(e.getMessage());
        //    // 不抛出异常,吃掉异常,记录日志
        //    log.error("交换机模式【direct】,key【error】,消费异常:{},消息体为:{}",e.getMessage(), user);
        //    // 第一种吃掉异常:throw e;
        //}

        // 第二种限定重试次数为:3,抛出异常
        try {
            int a = 0 / 0;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            // 不抛出异常,吃掉异常,记录日志
            log.error("交换机模式【direct】,key【error】,消费异常:{},消息体为:{}", e.getMessage(), user);
            ++errorCount;
            errorCountDetail.put(errorCount, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            throw e;
        }
    }

    @RabbitListener(bindings = {@QueueBinding(value = @Queue,//绑定临时队列,可以给名称,那么就不是临时队列
            exchange = @Exchange(value = "route_direct"),//绑定交换机名称和类型(默认是direct类类型)
            key = RouteCustomer.DEBUG)})
    public void debug(User user) {
        log.info("交换机模式【direct】,key【debug】,开始消费:{}", user);
    }

    @RabbitListener(bindings = {@QueueBinding(value = @Queue,//绑定临时队列,可以给名称,那么就不是临时队列
            exchange = @Exchange(value = "route_direct"),//绑定交换机名称和类型(默认是direct类类型)
            key = RouteCustomer.JSON)})
    public void json(@Payload User user) {
        log.info("交换机模式【direct】,key【json】,开始消费:{}", user);
    }

    @RabbitListener(bindings = {@QueueBinding(value = @Queue,//绑定临时队列,可以给名称,那么就不是临时队列
            exchange = @Exchange(value = "route_direct"),//绑定交换机名称和类型(默认是direct类类型)
            key = RouteCustomer.INFO)})
    public void info(@Payload User user) {
        log.info("交换机模式【direct】,key【info】,开始消费:{}", user);
    }

}
3.5.3 结果验证

在这里插入图片描述
在这里插入图片描述

3.6 主题模式(模式五)

在这里插入图片描述

简介:
从上图可以看出,P是生产者,X是交换机,单词部分是路由规则key,中间红色部分是消息缓冲区域(通道),C1、C2是消费者。
可以看出这个模式和路由模式是比较相似的,不同点是他的key,路由的规则,这里的路由规则是表达式(有点像是正则),官网也说明了。
*号代表一个单词,#号代号零个或多个单词
分析图中的路由规则:
生产者的路由key为:abc.orange.bcd 这个时候消息将分发到Q1, 因为Q1的匹配规则是:单词1个.orange.单词1个
生产者的路由key为:abc.bcd.rabbit 这个时候消息将分发到Q2, 因为Q2的匹配规则是:单词1个.单词1个.rabbit
生产者的路由key为:lazy 或者 lazy.abc 或者 lazy.bcd 或者 lazy.abc.bcd 这个时候消息将分发到Q2, 因为Q2的匹配规则是:lazy.单词(0个 或者 多个)

3.6.1 生产者
@Autowired
private RabbitTemplate rabbitTemplate;

/**
 * 第五种:主题模式
 */
@Test
public void topicTest() {
    rabbitTemplate.convertAndSend("topic", "user.1", "生产者 路由Key【user.1】 message");
    rabbitTemplate.convertAndSend("topic", "user.1.2", "生产者 路由Key【user.1.2】 message");
    rabbitTemplate.convertAndSend("topic", "user.admin.abc", "生产者 路由Key【user.admin.abc】 message");
    rabbitTemplate.convertAndSend("topic", "user.admin.1.2", "生产者 路由Key【user.admin.1.2】 message");
    rabbitTemplate.convertAndSend("topic", "user.admin", "生产者 路由Key【user.admin】 message");
    rabbitTemplate.convertAndSend("topic", "user.ordinary.1.2", "生产者 路由Key【user.ordinary.1.2】 message");
    rabbitTemplate.convertAndSend("topic", "user.ordinary.1", "生产者 路由Key【user.ordinary.1】 message");
}
3.6.2 消费者
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
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
@Slf4j
public class TopicCustomer {

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//绑定临时队列
                    exchange = @Exchange(value = "topic", type = ExchangeTypes.TOPIC),//绑定交换机
                    key = "user.*"
            )
    })
    public void topic1(String message) {
        log.info("topic1的key为:[user.*] 开始消费:{}" , message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//绑定临时队列,可以给名称,那么就不是临时队列
                    exchange = @Exchange(value = "topic", type = ExchangeTypes.TOPIC),//绑定交换机
                    key = "user.#"
            )
    })
    public void topic2(String message) {
        log.info("topic2的key为:[user.#] 开始消费:{}" , message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//绑定临时队列,可以给名称,那么就不是临时队列
                    exchange = @Exchange(value = "topic", type = ExchangeTypes.TOPIC),//绑定交换机
                    key = { "user.#", "user.*" } // 这个可以为数组,可以是多个key
            )
    })
    public void topic3(String message) {
        log.info("topic3的key为:[user.# , user.*] 开始消费:{}" , message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//绑定临时队列,可以给名称,那么就不是临时队列
                    exchange = @Exchange(value = "topic", type = ExchangeTypes.TOPIC),//绑定交换机
                    key = { "user.admin.#", "user.*.*", "user.ordinary.*"} // 这个可以为数组,可以是多个key
            )
    })
    public void topic4(String message) {
        log.info("topic4的key为:[user.admin.#, user.*.*, user.ordinary.*] 开始消费:{}" , message);
    }

    @RabbitListener(bindings = {
            @QueueBinding(
                    value = @Queue,//绑定临时队列,可以给名称,那么就不是临时队列
                    exchange = @Exchange(value = "topic", type = ExchangeTypes.TOPIC),//绑定交换机
                    key = "user.ordinary.*.*" // 这个可以为数组,可以是多个key
            )
    })
    public void topic5(String message) {
        log.info("topic5的key为:[user.ordinary.*.*] 开始消费:{}" , message);
    }
3.6.3 结果验证

在这里插入图片描述

3.6.4 分析

根据简介的路由规则而分析:
具体分析生产者user.admin这个路由key:从上图查看有四个消费者,如下:
user.*
user.admin.#, user.., user.ordinary.*
user.# , user.*
user.#
分析:
user.admin是两个单词
的可以是user.
#的可以是user.# 、 user.admin.#
可以看出结果都是符合路由匹配规则的,验证了简介中说明的*、#号的路由规则
疑问,你这边没有测试#为0的啊,user能不能匹配呢,好的,下面给予测试结果:
从图中可以看出,user.#确实是匹配了0 || n的匹配规则,而*就不行必须是要有1个单词完全匹配才行
在这里插入图片描述

结束语

本章节完成了,各位正在努力的程序员们,如果你们觉得本文章对您有用的话,或者是你学到了一些东西,期待您用漂亮,帅气的小手点个赞+关注,支持一下猿仁!
持续更新中…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TT-Code

多一分支持,多一分动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值