其他分享
首页 > 其他分享> > SpringBoot RabbitMQ 注解版 基本概念与基本案例

SpringBoot RabbitMQ 注解版 基本概念与基本案例

作者:互联网

前言

人间清醒

目录

Windows安装RabbitMQ

环境工具下载

rabbitMQ是Erlang语言开发的所以先下载Erlang;

RabbitMQ官网地址: https://www.rabbitmq.com/
Erlang下载: https://www.erlang.org/downloads

如果不 想使用最新版本可以使用我已经下载保存在云盘的环境: https://www.aliyundrive.com/s/Yts3ufpyk1W(otp_win64_23.0,rabbitmq-server-3.8.8)

Erlang环境安装

直接运行: otp_win64_23.0.exe 程序一直next即可,如需改变安装位置自行选择,安装完成后对系统环境变量新增ERLANG_HOME地址为:

C:\Program Files\erl-23.0

ErlangHome
双击系统变量path,点击“新建”,将%ERLANG_HOME%\bin加入到path中。
ErlangPath
win+R键,输入cmd,再输入erl,看到erlang版本号就说明erlang安装成功了。

RabbitMQ安装

直接运行: rabbitmq-server-3.8.8 程序一直next即可,如需改变安装位置自行选择.

RabbitMQ Web管理端安裝

进入安装后的RabbitMQ的sbin目录中(C:\Program Files\RabbitMQ Server\rabbitmq_server-3.8.8\sbin)
Cmd命令执行: rabbitmq-plugins enable rabbitmq_managementr

C:\Program Files\RabbitMQ Server\rabbitmq_server-3.8.8\sbin>rabbitmq-plugins enable rabbitmq_management
Enabling plugins on node rabbit@LX-P1DMPLUV:
rabbitmq_management
The following plugins have been configured:
  rabbitmq_management
  rabbitmq_management_agent
  rabbitmq_web_dispatch
Applying plugin configuration to rabbit@LX-P1DMPLUV...
Plugin configuration unchanged

常用命令:

# 启动RabbitMQ
rabbitmq-service start

# 停止RabbitMQ
rabbitmq-service stop

# 启用RabbitMQ Web可视化界面插件
rabbitmq-plugins enable rabbitmq_management

# 停用RabbitMQ Web可视化界面插件
rabbitmq-plugins disable rabbitmq_management

# 查看RabbitMQ状态
rabbitmqctl status

访问管理端页面,默认账号密码为: guest

可视化界面: http://127.0.0.1:15672/#/
RabbitMQ新增超级管理员

进入安装后的RabbitMQ的sbin目录中(C:\Program Files\RabbitMQ Server\rabbitmq_server-3.8.8\sbin)

# 创建用户root用户 密码为123456
rabbitmqctl add_user root 123456
# 为该用户分配所有权限
rabbitmqctl set_permissions -p / root ".*" ".*" ".*"
# 设置该用户为管理员角色
rabbitmqctl set_user_tags root administrator

RabbitMQ特点

RabbitMQ是一款使用Erlang语言开发的,实现AMQP(高级消息队列协议)的开源消息中间件。首先要知道一些RabbitMQ的特点:

RabbitMQ 3种常用交换机

RabbitMQ 5种常用模式

RabbitMQ名词解释

MQ适用场景

异步处理场景

如当一个站点新增用户时需要走以下流程:验证账号信息->用户入库->发送注册成功欢迎邮箱给用户;
从该流程中分析用户注册成功后首先期望的是能够成功登录上站点,而对于能否收到注册成功的邮件对于用户而言并不重要,
而邮件发送对于如遇到网络问题可能导致发送邮件缓慢从来导致整个用户注册流程响应很慢;
对于通知邮件发送对于功能而言并不重要的时候,这个时候就可以将该业务放在MQ中异步执行从而可以从一定程度上提升整个流程的性能。

应用解耦

如当一个站点新增用户时需要走以下流程:验证账号信息->用户入库->发送注册成功欢迎邮箱给用户;
通常通过系统划分会划分为:用户模块,消息模块;
以Spring Cloud的为例按照原始做法会在用户入库成功后会通过Feign调用消息模块的发送邮件接口,但是如果消息模块全集群宕机就会导致Feign请求失败从而导致业务不可用;
使用MQ就不会造成上述的问题,因为我们在用户注册完成后想消息模块对应的邮件发送业务队列去发送消息即可,队列会监督消息模块完成,如果完不成队列会一直监督,直到完成为止

流量削峰

秒杀和抢购等场景经常使用 MQ 进行流量削峰。活动开始时流量暴增,用户的请求写入 MQ,超过 MQ 最大长度丢弃请求,业务系统接收 MQ 中的消息进行处理,达到流量削峰、保证系统可用性的目的。
影响:MQ是排队执行的所以对性能有一定的影响,并且请求过多后会导致请求被丢弃问题

消息通讯

点对点或者订阅发布模式,通过消息进行通讯。如微信的消息发送与接收、聊天室等。

SpringBoot中使用RabbitMQ

工程创建&准备

说明该工程按照包区分同时担任生产者与消费者

POM导入依赖:

<dependencies>
    <!-- RabbitMQ依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <!-- 导入Web服务方便测试 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 代码简化工具 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

创建SpringBoot启动类:

@SpringBootApplication
public class SimpleRabbitMQCaseApplication {
    public static void main(String[] args) {
        SpringApplication.run(SimpleRabbitMQCaseApplication.class,args);
    }
}

创建applicatin.yaml:

server:
  port: 8021
spring:
  application:
    name: rabbitmq-simple-case
  #配置rabbitMq 服务器
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: root
    password: 123456
    virtual-host: / # 虚拟host 可以不设置,使用server默认host
    listener:
      simple:
        concurrency: 10 # 消费端的监听个数(即@RabbitListener开启几个线程去处理数据。)
        max-concurrency: 10 # 消费端的监听最大个数
        prefetch: 5
        acknowledge-mode: auto # MANUAL:手动处理 AUTO:自动处理
        default-requeue-rejected: true # 消费不成功的消息拒绝入队
        retry:
          enabled: true # 开启消息重试
          max-attempts: 5 # 重试次数
          max-interval: 10000   # 重试最大间隔时间
          initial-interval: 2000  # 重试初始间隔时间
简单队列生产消费

生产者:

/**
 * 简单队列消息生产
 * @author wuwentao
 */
@RestController
@RequestMapping("/simple/queue")
@AllArgsConstructor
public class SimpleQueueProducer {
    private RabbitTemplate rabbitTemplate;
    // 发送到的队列名称
    public static final String AMQP_SIMPLE_QUEUE = "amqp.simple.queue";

    /**
     * 发送简单消息
     * @param message 消息内容
     */
    @GetMapping("/sendMessage")
    public String sendMessage(@RequestParam(value = "message") String message){
        rabbitTemplate.convertAndSend(AMQP_SIMPLE_QUEUE, message);
        return "OK";
    }
}

消费者:

/**
 * 简单队列消息消费者
 * @author wuwentao
 */
@Component
@Slf4j
public class SimpleQueueConsumer {
    /**
     * 监听一个简单的队列,队列不存在时候会创建
     * @param content 消息
     */
    @RabbitListener(queuesToDeclare = @Queue(name = SimpleQueueProducer.AMQP_SIMPLE_QUEUE))
    public void consumerSimpleMessage(String content, Message message, Channel channel) throws IOException {
        // 通过Message对象解析消息
        String messageStr = new String(message.getBody());
        log.info("通过参数形式接收的消息:{}" ,content);
        //log.info("通过Message:{}" ,messageStr); // 可通过Meessage对象解析消息
        // channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); // 手动确认消息消费成功
        // channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); // 手动确认消息消费失败
    }
}

测试生成消息访问接口地址:

http://localhost:8021/simple/queue/sendMessage?message=这是一条简单的消息序号1
http://localhost:8021/simple/queue/sendMessage?message=这是一条简单的消息序号2
http://localhost:8021/simple/queue/sendMessage?message=这是一条简单的消息序号3

控制台打印消费信息:

2022-08-22 09:45:26.846  INFO 14400 --- [ntContainer#0-1] c.g.b.s.consumer.SimpleQueueConsumer     : 通过参数形式接收的消息:这是一条简单的消息序号1
2022-08-22 09:45:29.064  INFO 14400 --- [tContainer#0-10] c.g.b.s.consumer.SimpleQueueConsumer     : 通过参数形式接收的消息:这是一条简单的消息序号2
2022-08-22 09:45:31.441  INFO 14400 --- [ntContainer#0-4] c.g.b.s.consumer.SimpleQueueConsumer     : 通过参数形式接收的消息:这是一条简单的消息序号3

注意事项:在YAML中开启的配置acknowledge-mode为auto也是默认的所以消息不需要手动确认默认没有异常则消费成功,如果需要定制ACK方式可以将acknowledge-mode修改为MANUAL则要在消费完成后自行ACK或NACK否则将导致消息重复消费

Fanout Exchange 扇形交换机 广播模式

fanout模式也叫广播模式,每一条消息可以被绑定在同一个交换机上的所有队列的消费者消费

生产者:

@RestController
@RequestMapping("/exchange/fanout")
@AllArgsConstructor
public class ExchangeFanoutProducer {
    private RabbitTemplate rabbitTemplate;
    // 扇形交换机定义
    public static final String EXCHANGE_FANOUT = "exchange.fanout";
    // 绑定扇形交换机的队列1
    public static final String EXCHANGE_FANOUT_QUEUE_1 = "exchange.fanout.queue1";
    // 绑定扇形交换机的队列2
    public static final String EXCHANGE_FANOUT_QUEUE_2 = "exchange.fanout.queue2";

    /**
     * 发送扇形消息消息能够被所有绑定该交换机的队列给消费
     * @param message 消息内容
     */
    @GetMapping("/sendMessage")
    public String sendMessage(@RequestParam(value = "message") String message){
		// routingkey 在fanout模式不使用,会在direct和topic模式使用,所以这里给空
        rabbitTemplate.convertAndSend(EXCHANGE_FANOUT,"", message);
        return "OK";
    }
}

消费者:

这里定义两个消费者同时绑定同一个扇形交换机,这里主要声明交换机Type为ExchangeTypes.FANOUT

/**
 * 扇形交换机队列消费者
 * @author wuwentao
 */
@Component
@Slf4j
public class ExchangeFanoutConsumer {
    /**
     * 创建交换机并且绑定队列(队列1)
     *
     * @param content 内容
     * @param channel 通道
     * @param message 消息
     * @throws IOException      ioexception
     * @throws TimeoutException 超时异常
     */
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = ExchangeFanoutProducer.EXCHANGE_FANOUT, durable = "true", type = ExchangeTypes.FANOUT),
            value = @Queue(value = ExchangeFanoutProducer.EXCHANGE_FANOUT_QUEUE_1, durable = "true")
    ))
    @RabbitHandler
    public void exchangeFanoutQueue1(String content, Channel channel, Message message) {
        log.info("EXCHANGE_FANOUT_QUEUE_1队列接收到消息:{}",content);
    }

    /**
     * 创建交换机并且绑定队列(队列2)
     */
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = ExchangeFanoutProducer.EXCHANGE_FANOUT, durable = "true", type = ExchangeTypes.FANOUT),
            value = @Queue(value = ExchangeFanoutProducer.EXCHANGE_FANOUT_QUEUE_2, durable = "true")
    ))
    @RabbitHandler
    public void exchangeFanoutQueue2(String content, Channel channel, Message message) {
        log.info("EXCHANGE_FANOUT_QUEUE_2队列接收到消息:{}",content);
    }

}

测试生成消息访问接口地址:

http://localhost:8021/exchange/fanout/sendMessage?message=这是一条扇形交换机中的消息序号1
http://localhost:8021/exchange/fanout/sendMessage?message=这是一条扇形交换机中的消息序号2
http://localhost:8021/exchange/fanout/sendMessage?message=这是一条扇形交换机中的消息序号3

控制台打印消费信息:

2022-08-22 10:10:43.285  INFO 12016 --- [ntContainer#1-2] c.g.b.s.consumer.ExchangeFanoutConsumer  : EXCHANGE_FANOUT_QUEUE_2队列接收到消息:这是一条扇形交换机中的消息序号1
2022-08-22 10:10:43.285  INFO 12016 --- [ntContainer#0-7] c.g.b.s.consumer.ExchangeFanoutConsumer  : EXCHANGE_FANOUT_QUEUE_1队列接收到消息:这是一条扇形交换机中的消息序号1
2022-08-22 10:10:49.151  INFO 12016 --- [tContainer#0-10] c.g.b.s.consumer.ExchangeFanoutConsumer  : EXCHANGE_FANOUT_QUEUE_1队列接收到消息:这是一条扇形交换机中的消息序号2
2022-08-22 10:10:49.151  INFO 12016 --- [ntContainer#1-4] c.g.b.s.consumer.ExchangeFanoutConsumer  : EXCHANGE_FANOUT_QUEUE_2队列接收到消息:这是一条扇形交换机中的消息序号2
2022-08-22 10:10:54.254  INFO 12016 --- [ntContainer#0-6] c.g.b.s.consumer.ExchangeFanoutConsumer  : EXCHANGE_FANOUT_QUEUE_1队列接收到消息:这是一条扇形交换机中的消息序号3
2022-08-22 10:10:54.255  INFO 12016 --- [ntContainer#1-3] c.g.b.s.consumer.ExchangeFanoutConsumer  : EXCHANGE_FANOUT_QUEUE_2队列接收到消息:这是一条扇形交换机中的消息序号3
Direct Exchange 直连型交换机,

直连交换机与扇形交换机的区别在于,队列都是绑定同一个交换机,但是在队列上会添加routingkey标识,消费者会根据对应的不同routingkey消费对应的消息。

生产者:

@RestController
@RequestMapping("/exchange/direct")
@AllArgsConstructor
public class ExchangeDirectProducer {
    private RabbitTemplate rabbitTemplate;
    // 直连交换机定义
    public static final String EXCHANGE_DIRECT = "exchange.direct";
	// 直连交换机队列定义1
    public static final String EXCHANGE_DIRECT_QUEUE_1 = "exchange.direct.queue1";
    // 直连交换机队列定义2
    public static final String EXCHANGE_DIRECT_QUEUE_2 = "exchange.direct.queue2";
    // 直连交换机路由KEY定义1
    public static final String EXCHANGE_DIRECT_ROUTING_KEY_1 = "exchange.direct.routing.key1";
    // 直连交换机路由KEY定义2
    public static final String EXCHANGE_DIRECT_ROUTING_KEY_2 = "exchange.direct.routing.key2";

    /**
     * 发送消息到直连交换机并且指定对应routingkey
     * @param message 消息内容
     */
    @GetMapping("/sendMessage")
    public String sendMessage(@RequestParam(value = "message") String message,
                              @RequestParam(value = "routingkey") int routingkey){
        if(routingkey == 1){
            rabbitTemplate.convertAndSend(EXCHANGE_DIRECT,EXCHANGE_DIRECT_ROUTING_KEY_1, message);
        } else if (routingkey == 2){
            rabbitTemplate.convertAndSend(EXCHANGE_DIRECT,EXCHANGE_DIRECT_ROUTING_KEY_2, message);
        }else{
            return "非法参数!";
        }
        return "OK";
    }
}

消费者:

这里定义多个消费者同时绑定同一个直连交换机,这里主要声明交换机Type为ExchangeTypes.DIRECT,不声明则默认为DIRECT。

/**
 * 直连交换机队列消费者
 * @author wuwentao
 */
@Component
@Slf4j
public class ExchangeDirectConsumer {
    /**
     * 创建交换机并且绑定队列1(绑定routingkey1)
     *
     * @param content 内容
     * @param channel 通道
     * @param message 消息
     * @throws IOException      ioexception
     * @throws TimeoutException 超时异常
     */
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = ExchangeDirectProducer.EXCHANGE_DIRECT, durable = "true", type = ExchangeTypes.DIRECT),
            value = @Queue(value = ExchangeDirectProducer.EXCHANGE_DIRECT_QUEUE_1, durable = "true"),
            key = ExchangeDirectProducer.EXCHANGE_DIRECT_ROUTING_KEY_1
    ))
    @RabbitHandler
    public void exchangeDirectRoutingKey1(String content, Channel channel, Message message) {
        log.info("队列1 KEY1接收到消息:{}",content);
    }

    /**
     * 创建交换机并且绑定队列2(绑定routingkey2)
     */
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = ExchangeDirectProducer.EXCHANGE_DIRECT, durable = "true", type = ExchangeTypes.DIRECT),
            value = @Queue(value = ExchangeDirectProducer.EXCHANGE_DIRECT_QUEUE_2, durable = "true"),
            key = ExchangeDirectProducer.EXCHANGE_DIRECT_ROUTING_KEY_2
    ))
    @RabbitHandler
    public void exchangeDirectRoutingKey2(String content, Channel channel, Message message) {
        log.info("队列2 KEY2接收到消息:{}",content);
    }

}

测试生成消息访问接口地址:

http://localhost:8021/exchange/direct/sendMessage?routingkey=1&message=这是一条发给路由key为1的消息
http://localhost:8021/exchange/direct/sendMessage?routingkey=2&message=这是一条发给路由key为2的消息

控制台打印消费信息:

2022-08-22 10:37:22.173  INFO 4380 --- [ntContainer#0-1] c.g.b.s.consumer.ExchangeDirectConsumer  : 队列1 KEY1接收到消息:这是一条发给路由key为1的消息
2022-08-22 10:37:26.882  INFO 4380 --- [ntContainer#1-3] c.g.b.s.consumer.ExchangeDirectConsumer  : 队列2 KEY2接收到消息:这是一条发给路由key为2的消息
Topic Exchange 主题交换机

这个交换机其实跟直连交换机流程差不多,但是它的特点就是在它的路由键和绑定键之间是有规则的;规则如下:
Topic交换机接收的消息RoutingKey必须是多个单词,以 . 分割
Topic交换机与队列绑定时的routingKey可以指定通配符

#:代表0个或多个词
*:代表1个词

生产者:

@RestController
@RequestMapping("/exchange/topic")
@AllArgsConstructor
public class ExchangeTopicProducer {
    private RabbitTemplate rabbitTemplate;
    // 主題交换机定义
    public static final String EXCHANGE_TOPIC = "exchange.topic";
    // 主題交换机队列定义1
    public static final String EXCHANGE_TOPIC_QUEUE_1 = "exchange.topic.queue1";
    // 主題交换机队列定义1
    public static final String EXCHANGE_TOPIC_QUEUE_2 = "exchange.topic.queue2";

    // 主題交换机队列路由Key定义1
    public static final String EXCHANGE_TOPIC_ROUTING1_KEY_1 = "#.routingkey.#";
    // 主題交换机队列路由Key定义2
    public static final String EXCHANGE_TOPIC_ROUTING2_KEY_2 = "routingkey.*";

    // 案例KEY1 可以被EXCHANGE_TOPIC_ROUTING1_KEY_1匹配不能被EXCHANGE_TOPIC_ROUTING2_KEY_2匹配
    public static final String EXCHANGE_TOPIC_CASE_KEY_1 = "topic.routingkey.case1";
    // 案例KEY2 同时可以被EXCHANGE_TOPIC_ROUTING1_KEY_1和EXCHANGE_TOPIC_ROUTING2_KEY_2匹配
    public static final String EXCHANGE_TOPIC_CASE_KEY_2 = "routingkey.case2";

    /**
     * 发送消息到主题交换机并且指定对应可通配routingkey
     * @param message 消息内容
     */
    @GetMapping("/sendMessage")
    public String sendMessage(@RequestParam(value = "message") String message,
                              @RequestParam(value = "routingkey") int routingkey){
        if(routingkey == 1){
            // 同时匹配 topic.routingkey.case1 和 routingkey.case2
            rabbitTemplate.convertAndSend(EXCHANGE_TOPIC,EXCHANGE_TOPIC_CASE_KEY_1, message);
        } else if (routingkey == 2){
            // 只能匹配 routingkey.case2
            rabbitTemplate.convertAndSend(EXCHANGE_TOPIC,EXCHANGE_TOPIC_CASE_KEY_2, message);
        }else{
            return "非法参数!";
        }
        return "OK";
    }
}

消费者:

这里定义多个消费者同时绑定同一个直主题交换机,这里主要声明交换机Type为ExchangeTypes.TOPIC,当routingkey发送的消息能够被消费者给匹配仅能够接收到消息。

@Component
@Slf4j
public class ExchangeTopicConsumer {
    /**
     * 创建交换机并且绑定队列1(绑定routingkey1)
     *
     * @param content 内容
     * @param channel 通道
     * @param message 消息
     * @throws IOException      ioexception
     * @throws TimeoutException 超时异常
     */
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = ExchangeTopicProducer.EXCHANGE_TOPIC, durable = "true", type = ExchangeTypes.TOPIC),
            value = @Queue(value = ExchangeTopicProducer.EXCHANGE_TOPIC_QUEUE_1, durable = "true"),
            key = ExchangeTopicProducer.EXCHANGE_TOPIC_ROUTING1_KEY_1
    ))
    @RabbitHandler
    public void exchangeTopicRoutingKey1(String content, Channel channel, Message message) {
        log.info("#号统配符号队列1接收到消息:{}",content);
    }

    /**
     * 创建交换机并且绑定队列2(绑定routingkey2)
     */
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = ExchangeTopicProducer.EXCHANGE_TOPIC, durable = "true", type = ExchangeTypes.TOPIC),
            value = @Queue(value = ExchangeTopicProducer.EXCHANGE_TOPIC_QUEUE_2, durable = "true"),
            key = ExchangeTopicProducer.EXCHANGE_TOPIC_ROUTING2_KEY_2
    ))
    @RabbitHandler
    public void exchangeTopicRoutingKey2(String content, Channel channel, Message message) {
        log.info("*号统配符号队列2接收到消息:{}",content);
    }

}

测试生成消息访问接口地址:

http://localhost:8021/exchange/topic/sendMessage?routingkey=1&message=前后多重匹配
http://localhost:8021/exchange/topic/sendMessage?routingkey=2&message=后一个词匹配

控制台打印消费信息:

2022-08-22 15:10:50.444  INFO 1376 --- [ntContainer#4-8] c.g.b.s.consumer.ExchangeTopicConsumer   : #号统配符号队列1接收到消息:前后多重匹配
2022-08-22 15:10:55.118  INFO 1376 --- [ntContainer#5-8] c.g.b.s.consumer.ExchangeTopicConsumer   : *号统配符号队列2接收到消息:后一个词匹配
2022-08-22 15:10:55.119  INFO 1376 --- [ntContainer#4-9] c.g.b.s.consumer.ExchangeTopicConsumer   : #号统配符号队列1接收到消息:后一个词匹配
手动ACK与消息确认机制

新增SpringBoot配置文件YAML,这里主要将自动ACK修改为手工ACK并且开启消息确认模式与消息回退:

spring:
  rabbitmq:
    listener:
        acknowledge-mode: manual # MANUAL:手动处理 AUTO:自动处理
    # NONE值是禁用发布确认模式,是默认值
    # CORRELATED值是发布消息成功到交换器后会触发回调方法,如1示例
    # SIMPLE值经测试有两种效果,其一效果和CORRELATED值一样会触发回调方法,其二在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果,根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker;
    publisher-confirm-type: simple #消息确认机制
    publisher-returns: true # 消息回退确认机制

定义消息回调确认实现类:

/**
 * 消费者确认收到消息后,手动ack回执回调处理
 * @author wuwentao
 */
@Slf4j
@Component
public class MessageConfirmCallback implements RabbitTemplate.ConfirmCallback {
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        log.info("===================================================");
        log.info("消息确认机制回调函数参数信息如下:");
        log.info("ACK状态:{}",ack);
        log.info("投递失败原因:{}",cause);
        log.info("===================================================");
    }
}

消费者:

/**
 * RabbitMQ Message 回调地址消费者测试
 * @author wuwentao
 */
@Component
@Slf4j
public class MessagesCallbackConsumer {
    @RabbitListener(bindings = @QueueBinding(
            exchange = @Exchange(value = MessagesCallbackProducer.MESSAGE_CALLBACK_EXCHANGE, durable = "true", type = ExchangeTypes.DIRECT),
            value = @Queue(value = MessagesCallbackProducer.MESSAGE_CALLBACK_QUEUE, durable = "true"),
            key = MessagesCallbackProducer.MESSAGE_CALLBACK_ROUTINGKEY
    ))
    @RabbitHandler
    public void consumer(String content, Channel channel, Message message) throws IOException {
        if("成功".equals(content)){
            log.info("消息处理成功:{}",content);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); // 手动确认消息消费成功
        }else{
            if(message.getMessageProperties().getRedelivered()){
                log.info("消息已被处理过了请勿重复处理消息:{}",content);
                channel.basicReject(message.getMessageProperties().getDeliveryTag(), false); // 拒绝该消息,消息会被丢弃,不会重回队列
            }else{
                log.info("消息处理失败等待重新处理:{}",content);
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }
}

生产者:

/**
 * 消息回调机制测试
 * @author wuwentao
 */
@RestController
@RequestMapping("/message/callback")
@AllArgsConstructor
public class MessagesCallbackProducer {
    private RabbitTemplate rabbitTemplate;
    private MessageConfirmCallback messageConfirmCallback;
    // 发送到的队列名称
    public static final String MESSAGE_CALLBACK_QUEUE = "amqp.message.callback.queue";
    public static final String MESSAGE_CALLBACK_EXCHANGE = "amqp.message.callback.exchange";
    public static final String MESSAGE_CALLBACK_ROUTINGKEY = "amqp.message.callback.routingkey";
    /**
     * 测试消息确认机制
     * @param message 消息内容
     */
    @GetMapping("/sendMessage")
    public String sendMessage(@RequestParam(value = "message") String message){
        // 设置失败和确认回调函数
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback(messageConfirmCallback);
        //构建回调id为uuid
        String callBackId = UUID.randomUUID().toString();
        CorrelationData correlationData = new CorrelationData(callBackId);
        if("失败的消息".equals(message)){
            // 写一个不存的交换机器 和不存在的路由KEY
            rabbitTemplate.convertAndSend("sdfdsafadsf","123dsfdasf",message,
                    msg -> {
                        msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                        return msg;
                    },correlationData);
        }else{
            rabbitTemplate.convertAndSend(MESSAGE_CALLBACK_EXCHANGE,MESSAGE_CALLBACK_ROUTINGKEY,message,
                    msg -> {
                        msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
                        return msg;
                    },correlationData);
        }
        return "OK";
    }
}

测试生成消息访问接口地址:

# 发送找不到交换机的消息
http://localhost:8021/message/callback/sendMessage?message=失败的消息
# 发送手动ACK成功的消息
http://localhost:8021/message/callback/sendMessage?message=成功
# 发送手动ACK失败的消息
http://localhost:8021/message/callback/sendMessage?message=失败

控制台打印消费信息:

2022-08-24 09:11:50.122  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : ===================================================
2022-08-24 09:11:50.122  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : 消息确认机制回调函数参数信息如下:
2022-08-24 09:11:50.123  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : ACK状态:false
2022-08-24 09:11:50.127  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : 投递失败原因:channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no exchange 'sdfdsafadsf' in vhost '/', class-id=60, method-id=40)
2022-08-24 09:11:50.127  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : ===================================================
2022-08-24 09:12:02.704  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : ===================================================
2022-08-24 09:12:02.705  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : 消息确认机制回调函数参数信息如下:
2022-08-24 09:12:02.705  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : ACK状态:true
2022-08-24 09:12:02.705  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : 投递失败原因:null
2022-08-24 09:12:02.705  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : ===================================================
2022-08-24 09:12:02.735  INFO 11440 --- [ntContainer#6-1] c.g.b.s.c.MessagesCallbackConsumer       : 消息处理成功:成功
2022-08-24 09:12:16.680  INFO 11440 --- [ntContainer#6-4] c.g.b.s.c.MessagesCallbackConsumer       : 消息处理失败等待重新处理:失败
2022-08-24 09:12:16.688  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : ===================================================
2022-08-24 09:12:16.689  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : 消息确认机制回调函数参数信息如下:
2022-08-24 09:12:16.689  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : ACK状态:true
2022-08-24 09:12:16.689  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : 投递失败原因:null
2022-08-24 09:12:16.689  INFO 11440 --- [nectionFactory2] c.g.b.s.callback.MessageConfirmCallback  : ===================================================
2022-08-24 09:12:16.693  INFO 11440 --- [ntContainer#6-7] c.g.b.s.c.MessagesCallbackConsumer       : 消息已被处理过了请勿重复处理消息:失败
案例源代码

https://gitee.com/SimpleWu/blogs-examples/tree/master/rabbitmq-simple-case

标签:SpringBoot,EXCHANGE,队列,RabbitMQ,交换机,消息,注解,message,public
来源: https://www.cnblogs.com/SimpleWu/p/16618662.html