RabbitMQ SpringBoot 消息确认机制 应用

一.概念

1.1消息的确认(Confirm):

是指生产者投递消息后,如果 Broker 收到消息,则会给我们生产者一个应答。生 产者进行接收应答,用来确定这条消息是否正常的发送到 Broker ,这种方式也是消息的可靠性投递的核心保障!

Confirm 确认机制流程图:
在这里插入图片描述

1.2 Return 消息机制

用于处理一些不可路 由的消息!

消息生产者,通过指定一个 Exchange 和 Routingkey,把消息送达到某一个队列中去,然后我们的消费者监听队列,进行消费处理操作!但是在某些情况下,如果我们在发送消息的时候,当前的 exchange 不存在或者指定的路由 key 路由不到,这个时候如果我们需要监听这种不可达的消息,就要使用 Return !

Return 消息机制流程图:
在这里插入图片描述

二.rabbitmq-produce的改动

项目使用上一篇中的项目 rabbitmq-produce、rabbitmq-consumer

2.1 修改配置文件application.yml

代码如下:

server:
  port: 8783

spring:
  #配置程序名为rabbitmq-produce-learn
  application:
    name: rabbitmq-produce-learn
  #配置rabbitMq 服务器
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    #虚拟host 可以不设置,使用server默认host
    #virtual-host: JCcccHost
    #支持发布确认
    publisher-confirms: true
    #支持发布返回
    publisher-returns: true
    template:
      mandatory: true
    listener:
      direct:
        prefetch: 0
        #采用手动应答
        acknowledge-mode: manual
      simple:
        prefetch: 0  #RabbitMQ 将消息顺序发送给多个消费者有两种模式(公平分发、轮询模式),
                     #区别在于公平分发的prefetch默认是1,如果设置为0就是轮询模式。
        #确认模式
        #采用手动应答
        #none:不确认,不会发送任何ack
        #manual:手动确认,发送端和客户端都需要手动确认
        #auto:自动确认,就是自动发ack,除非抛异常。
        acknowledge-mode: manual

2.2 在rabbitmq-produce中,新增一个RabbitComfirmAndReturnConfig配置类

RabbitComfirmAndReturnConfig的代码如下:

package com.example.rabbitmqproduce.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * rabbitmq
 * comfirm 消息确认机制  和 Return 消息机制  配置类
 * 消息的确认,是指生产者投递消息后,如果 Broker 收到消息,则会给我们生产者一个应答。生
 * 产者进行接收应答,用来确定这条消息是否正常的发送到 Broker ,这种方式也是消息的可靠性投递的核心保障!
 *
 *
 * Return 消息机制 用于处理一些不可路 由的消息!
 *消息生产者,通过指定一个 Exchange 和 Routingkey,把消息送达到某一个队列中去,然后我们的消费者监听队列,进行消费处理操作!
 *但是在某些情况下,如果我们在发送消息的时候,当前的 exchange 不存在或者指定的路由 key 路由不到,这个时候如果我们需要监听这种不可达的消息,就要使用 Return !
 *在基础API中有一个关键的配置项:Mandatory:如果为 true,则监听器会接收到路由不可达的消息,然后进行后续处理,如果为 false,那么 broker 端自动删除该消息!
 */
@Configuration
public class RabbitComfirmAndReturnConfig {

    private Logger logger = LoggerFactory.getLogger(RabbitComfirmAndReturnConfig.class);

    @Autowired
    private CachingConnectionFactory connectionFactory;

    public final static String ackQueueName = "ackQueue";

    public static final String ackExchangeName = "ackExchange";

    private static final String ackBindingKey = "ackRouting";

    @Bean
    public Queue ackQueue() {
        return new Queue(ackQueueName);
    }


    @Bean
    public DirectExchange ackExchange() {
        return new DirectExchange(ackExchangeName);
    }

    @Bean
    public Binding bindingExchangeAckMessage() {
        return BindingBuilder.bind(ackQueue()).to(ackExchange()).with(ackBindingKey);
    }


    /**
     * 定制化 RabbitTemplate 模版
     * @return
     */
    @Bean
    public RabbitTemplate rabbitTemplate() {

        //若使用confirm-callback或return-callback,必须在配置文件yml 配置publisherConfirms或publisherReturns为true
        connectionFactory.setPublisherConfirms(true);
        connectionFactory.setPublisherReturns(true);
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);

        //必须设置为 true,不然当 发送到交换器成功,但是没有匹配的队列,不会触发 ReturnCallback 回调
        //而且 ReturnCallback 比 ConfirmCallback 先回调,意思就是 ReturnCallback 执行完了才会执行 ConfirmCallback
        rabbitTemplate.setMandatory(true);

        //设置 ConfirmCallback 回调   配置文件yml需要配置 publisher-confirms: true
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            // 如果发送到交换器都没有成功(比如说删除了交换器),ack 返回值为 false
            // 如果发送到交换器成功,但是没有匹配的队列(比如说取消了绑定),ack 返回值为还是 true (这是一个坑,需要注意)
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                String messageId = correlationData.getId();
                if (ack) {
                    logger.info("confirm:"+messageId);
                    logger.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause);
                }else {
                    logger.info("confirm:"+messageId);
                    logger.info("消息发送失败:correlationData({}),ack({}),cause({})", correlationData, ack, cause);
                }
            }
        });

        //设置 ReturnCallback 回调   配置文件yml需要配置 publisher-returns: true
        //如果发送到交换器成功,但是没有匹配的队列,就会触发这个回调
        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            @Override
            public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
                logger.info("message:"+message);
                logger.warn("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message);
            }
        });
        return rabbitTemplate;
    }
}

2.3 新建消息生产者DirectExchangeProduce

DirectExchangeProduce的代码如下:

package com.example.rabbitmqproduce.produce;

import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 发送信息确认ack
 * @Component 注入到Spring容器中
 */
@Component
public class RabbitAckProduce {

    //注入一个 RabbitTemplate 来发布消息
    @Autowired
    private RabbitTemplate rabbitTemplate;

    private Logger logger = LoggerFactory.getLogger(RabbitAckProduce.class);

    private static final String ackRouteKey = "ackRouting";

    private static final String ackExchangeName = "ackExchange";

    /**
     * 发送消息
     */
    public void sendMessage() {
        String messageId = String.valueOf(UUID.randomUUID());
        String messageData = "hello!亚索 面对疾风吧";
        String createTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        Map<String,Object> map=new HashMap<>();
        map.put("messageId",messageId);
        map.put("messageData",messageData);
        map.put("createTime",createTime);
        //CorrelationData用于confirm机制里的回调确认
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        //将消息携带绑定键值:directRouting 发送到交换机directExchange
        rabbitTemplate.convertAndSend(ackExchangeName,ackRouteKey, map,correlationData);
        logger.info("mq消息发送结束==》{}", map.toString());
    }
}

2.3 写个测试的TestController类

代码如下:

package com.example.rabbitmqproduce.controller;


import com.example.rabbitmqproduce.produce.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("TestController")
public class TestController {

    private static final Logger logger = LoggerFactory.getLogger(TestController.class);

    @Autowired
    private RabbitMqProduce rabbitMqProduce;

    @Autowired
    private DirectExchangeProduce directExchangeProduce;

    @Autowired
    private FanoutExchangeProduce fanoutExchangeProduce;

    @Autowired
    private TopicExchangeProduce topicExchangeProduce;

    @Autowired
    private RabbitAckProduce rabbitAckProduce;

    /**
     * 测试基本消息模型(简单队列)
     */
    @RequestMapping(value = "/testSimpleQueue", method = RequestMethod.POST)
    public void testSimpleQueue() {
        logger.info("测试基本消息模型(简单队列)SimpleQueue---开始");
        for (int i = 0; i < 10; i++) {
            rabbitMqProduce.sendMessage();
        }
        logger.info("测试基本消息模型(简单队列)SimpleQueue---结束");
    }


    /**
     * 测试 Direct-exchange模式
     */
    @RequestMapping(value = "/directExchangeTest", method = RequestMethod.POST)
    public void directExchangeTest() {
        logger.info("测试 Direct-exchange模式 队列名为directQueue---开始");
        for (int i = 0; i < 10; i++) {
            directExchangeProduce.sendMessage();
        }
        logger.info("测试 Direct-exchange模式 队列名为directQueue---结束");
    }


    /**
     * 测试 Fanout-exchange模式
     */
    @RequestMapping(value = "/fanoutExchangeTest", method = RequestMethod.POST)
    public void fanoutExchangeTest() {
        logger.info("测试 fanout-exchange模式 队列名为fanoutQueue---开始");
        fanoutExchangeProduce.sendMessage();
        logger.info("测试 fanout-exchange模式 队列名为fanoutQueue---结束");
    }


    /**
     * 测试 Topic-exchange模式   topicA 和 topicB
     */
    @RequestMapping(value = "/topictExchangeTest", method = RequestMethod.POST)
    public void topictExchangeTest() {
        logger.info("测试 topict-exchange模式 队列名为topictQueueNameA---开始");
        topicExchangeProduce.sendMessageTopicA();
        logger.info("测试 topict-exchange模式 队列名为topictQueueNameA---结束");

        logger.info("测试 topict-exchange模式 队列名为topictQueueNameB---开始");
        topicExchangeProduce.sendMessageTopicB();
        logger.info("测试 topict-exchange模式 队列名为topictQueueNameB---结束");
    }


    /**
     * 测试 ack模式
     */
    @RequestMapping(value = "/ackTest", method = RequestMethod.POST)
    public void ackTest() {
        logger.info("测试 ack 队列名为ackQueue---开始");
        rabbitAckProduce.sendMessage();
        logger.info("测试 ack 队列名为ackQueue---结束");
    }

}

三.rabbitmq-consumer的改动

3.1 新建消息消费者RabbitAckConsumer类

RabbitAckConsumer代码如下:

package com.example.rabbitmqconsumer.consumer;

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

/**
 * 收到信息确认ack
 * @RabbitListener(queues = "ackQueue") 监听名为ackQueue的队列
 */

@Component
@RabbitListener(queues = "ackQueue")
public class RabbitAckConsumer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private Logger logger = LoggerFactory.getLogger(RabbitAckConsumer.class);

    /**
     * 消费消息
     * @RabbitHandler 代表此方法为接受到消息后的处理方法
     */
    @RabbitHandler
    public void receiveMessage(Map msg , Message message , Channel channel) throws IOException {
        // 采用手动应答模式, 手动确认应答更为安全稳定
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), true);
        logger.info("接收到的消息:---->" + msg.toString());
    }

}

四.测试

首先启动生产者rabbitmq-produce项目。在postman或浏览器上访问:
http://localhost:8783/TestController/ackTest POST请求
这时可以在rabbitmq-produce的控制台可以看到
在这里插入图片描述

然后再启动消费者rabbitmq-consumer工程,在rabbitmq-consumer可以看到:
在这里插入图片描述

下一章,学习 消费端限流、TTL、死信队列

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

刘德华一不小心就打代码

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值