引言

RabbitMQ的模型是生产者发送信息到 Broker (代理),消费者从 Broker 中取出信息。但是生产者怎么知道消息是否真的发送到 Broker 中了呢?Broker 又怎么知道消息到底有没有被消费者消费?

如果由于网络原因出现故障,生产者生产的消息未到达 Broker 或者 Broker 的消息被虚假消费,而它们又不知道,就会产生很严重的问题,如重复消费等。

RabbitMQ的消息确认流程

从图中可以看出:

消息确认机制分为生产者确认和消费者确认

  • ConfirmCallback 生产者
  • ReturnCallback 生产者
  • ACK 消费者

生产者确认

  • 消息到达RabbitMQ的Exchange:Exchange向生产者发送Confirm确认。成功抑或失败都会返回一个confirmCallback
  • 消息成功达到Exchange,但是从Exchange投递Queue失败:向生产者返回一个returnCallback。只有失败才会返回

消费者确认

  • 消费者收到消息后需要对 RabbitMQ Server 进行消息 ACK 确认,RabbitMQ 根据确认信息决定是删除队列中的该信息还是重新发送

代码实现

生产者确认

重点在于生产者重写下面两个方法

  • rabbitMQTemplate.setConfirmCallback

  • rabbitMQTemplate.setReturnCallback

  1. 开启生产者消息确认

    spring:
    rabbitmq:
    host: localhost
    port: 5672
    virtual-host: /
    username: root
    password: root
    # 开启两个模式的生产者消息确认
    publisher-confirm-type: simple
    publisher-returns: true
  2. 声明交换机、队列,绑定交换机和队列

    @Configuration
    public class RabbitMQConfig { private static final String SB_TOPIC_EXCHANGE="sb_topic_exchange";
    private static final String SB_TOPIC_QUEUE="sb_topic_queue1"; // 注入交换机 topic类型
    @Bean("topicExchange")
    public Exchange topicExchange(){
    return ExchangeBuilder.topicExchange(SB_TOPIC_EXCHANGE).durable(true)
    .autoDelete().build();
    }
    // 声明队列
    @Bean
    public Queue queue1(){
    return QueueBuilder.durable(SB_TOPIC_QUEUE).build();
    } // 绑定队列和交换机
    @Bean
    public Binding exchangQueue(@Qualifier("queue1") Queue queue, @Qualifier("topicExchange") Exchange exchange){
    return BindingBuilder.bind(queue).to(exchange).with("user.#").noargs();
    }
    }
  3. 创建消费者

    @Component
    @RabbitListener(queues = "sb_topic_queue1")
    public class Consumer { @RabbitHandler
    public void testPublishConfirm(String msg) {
    System.out.println("收到的信息:"+msg);
    }
    }
  4. 创建生产者

    创建生产者发送消息到消息队列,模拟两种异常情况

    @SpringBootTest
    class RabiitmqSpringbootApplicationTests { @Autowired
    RabbitTemplate template; @Test
    void testConfirmTrue() {
    // 设置confirm回调函数
    template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    @Override
    public void confirm(CorrelationData correlationData, boolean b, java.lang.String s) {
    if (b) System.out.println("消息发送成功");
    else System.out.println("消息发送失败");
    } });
    // 模拟生产者发送信息--正常情况
    template.convertAndSend("sb_topic_exchange","user.info","日志级别:info;日志模块:user;日志信息:*****");
    } @Test
    void testConfirmFalse() {
    // 设置confirm回调函数
    template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    @Override
    public void confirm(CorrelationData correlationData, boolean b, java.lang.String s) {
    if (b) System.out.println("消息发送成功");
    else System.out.println("消息发送失败");
    } });
    // 模拟生产者发送信息
    // 不存在的交换机--异常情况
    template.convertAndSend("sb_topic_exchange_noexist","user.info","日志级别:info;日志模块:user;日志信息:*****");
    } @Test
    void testReturnFalse() {
    // 设置return回调函数
    template.setReturnCallback(new RabbitTemplate.ReturnCallback() {
    @Override
    public void returnedMessage(Message message, int i, java.lang.String s, java.lang.String s1, java.lang.String s2) {
    System.out.println(message.toString());
    System.out.println(s+"*********");
    } });
    template.setMandatory(true);
    // 模拟生产者发送信息
    // 正确的交换机 错误的routekey -- 异常情况
    template.convertAndSend("sb_topic_exchange","noexist.user.info","日志级别:info;日志模块:user;日志信息:*****");
    }
    }

消费者确认

重点在于消费者的下面两个方法

  • channel.basicAck 消费者签收
  • channel.basicNAck 消费者拒绝签收
  1. 开启消费者确认模式

    spring:
    rabbitmq:
    host: localhost
    port: 5672
    virtual-host: /
    username: root
    password: root
    # 设置消费端手动签收
    listener:
    direct:
    acknowledge-mode: manual
    simple:
    acknowledge-mode: manual
  2. 创建消费者

    /**
    * 注入消费者--手动签到
    */
    @Component
    @RabbitListener(queues = "sb_topic_queue1")
    public class Consumer2 { @RabbitHandler
    public void testComsumer(String msg, Channel channel, Message message) throws InterruptedException, IOException {
    // 消费端设置手动签收代码
    try {
    System.out.println(msg);
    // 正常签收,mq收到此消息被正常签收后即可从队列中删除vi信息
    // 是哟了那个channel的方法
    // 第一个参数是deliverytag 标识哪条信息 第二个参数是是否批量签收
    // int i=2/0; 模拟异常
    channel.basicAck(message.getMessageProperties().getDeliveryTag(),true);
    System.out.println("消费者签收了该信息,服务器你可以删了");
    }catch (Exception e){
    // 异常拒绝签收,让mq重发此信息
    System.out.println("该信息丢了,给我重发");
    channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,true);
    // 该信息丢了,但是不需要你重发
    // channel.basicNack(message.getMessageProperties().getDeliveryTag(),true,false);
    }
    }
    }
  3. 创建生产者

    @SpringBootTest
    class RabiitmqSpringbootApplicationTests { @Autowired
    RabbitTemplate template; @Test
    void testConsumerAck() {
    template.convertAndSend("sb_topic_exchange","noexist.user.info","日志级别:info;日志模块:user;日志信息:*****");
    }
    }

参考

消息队列RabbitMQ(三):消息确认机制的更多相关文章

  1. RabbitMQ消息发布和消费的确认机制

    前言 新公司项目使用的消息队列是RabbitMQ,之前其实没有在实际项目上用过RabbitMQ,所以对它的了解都谈不上入门.趁着周末休息的时间也猛补习了一波,写了两个窗体应用,一个消息发布端和消息消费 ...

  2. 消息队列——RabbitMQ学习笔记

    消息队列--RabbitMQ学习笔记 1. 写在前面 昨天简单学习了一个消息队列项目--RabbitMQ,今天趁热打铁,将学到的东西记录下来. 学习的资料主要是官网给出的6个基本的消息发送/接收模型, ...

  3. ASP.NET Core消息队列RabbitMQ基础入门实战演练

    一.课程介绍 人生苦短,我用.NET Core!消息队列RabbitMQ大家相比都不陌生,本次分享课程阿笨将给大家分享一下在一般项目中99%都会用到的消息队列MQ的一个实战业务运用场景.本次分享课程不 ...

  4. 消息队列rabbitmq rabbitMQ安装

    消息队列rabbitmq   12.1 rabbitMQ 1. 你了解的消息队列 生活里的消息队列,如同邮局的邮箱, 如果没邮箱的话, 邮件必须找到邮件那个人,递给他,才玩完成,那这个任务会处理的很麻 ...

  5. 消息队列RabbitMQ的安装配置与PHP中的使用

    一.RabbitMQ安装 windows安装 下载地址: https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.3/ra ...

  6. RabbitMQ(三):消息持久化策略

    原文:RabbitMQ(三):消息持久化策略 一.前言 在正常的服务器运行过程中,时常会面临服务器宕机重启的情况,那么我们的消息此时会如何呢?很不幸的事情就是,我们的消息可能会消失,这肯定不是我们希望 ...

  7. SpringBoot(八) Spring和消息队列RabbitMQ

    概述 1.大多数应用中,可以通过消息服务中间件来提升系统异步能力和拓展解耦能力. 2.消息服务中的两个重要概念:消息代理(Message broker)和目的地(destination) 当消息发送者 ...

  8. (一)RabbitMQ消息队列-RabbitMQ的优劣势及产生背景

    原文:(一)RabbitMQ消息队列-RabbitMQ的优劣势及产生背景 本篇并没有直接讲到技术,例如没有先写个Helloword.我想在选择了解或者学习一门技术之前先要明白为什么要现在这个技术而不是 ...

  9. 消息队列rabbitmq/kafka

    12.1 rabbitMQ 1. 你了解的消息队列 rabbitmq是一个消息代理,它接收和转发消息,可以理解为是生活的邮局.你可以将邮件放在邮箱里,你可以确定有邮递员会发送邮件给收件人.概括:rab ...

  10. 消息队列rabbitmq的五种工作模式(go语言版本)

    前言:如果你对rabbitmq基本概念都不懂,可以移步此篇博文查阅消息队列RabbitMQ 一.单发单收 二.工作队列Work Queue 三.发布/订阅 Publish/Subscribe 四.路由 ...

随机推荐

  1. PTA 找出不是两个数组共有的元素

    7-2 找出不是两个数组共有的元素 (20 分)   给定两个整型数组,本题要求找出不是两者共有的元素. 输入格式: 输入分别在两行中给出两个整型数组,每行先给出正整数N(≤),随后是N个整数,其间以 ...

  2. python3 elf文件解析

    原地址:https://github.com/guanchao/elfParser 作者是用python2写的,现在给出我修改后的python3版本.(测试发现有bug,以后自己写个,0.0) 1 # ...

  3. 教你如何用Python模拟http请求(GET,POST)

    模拟http请求有什么用呢? 我们现在使用的所有需要使用网络的:软件 应用 app 网站里面的绝大部分功能都是通过http协议来工作的 什么是http协议? http协议,超文本传输协议(HTTP,H ...

  4. ZooKeeper 会话的秘密

    本文作者:HelloGitHub-老荀 Hi,这里是 HelloGitHub 推出的 HelloZooKeeper 系列,免费开源.有趣.入门级的 ZooKeeper 教程,面向有编程基础的新手. 项 ...

  5. [高精度]P1096 Hanoi 双塔问题

    Hanoi 双塔问题 题目描述 给定A.B.C三根足够长的细柱,在A柱上放有2n个中间有孔的圆盘,共有n个不同的尺寸,每个尺寸都有两个相同的圆盘,注意这两个圆盘是不加区分的(下图为n=3的情形). 现 ...

  6. mysql大于当前时间置顶并按升序排序,小于当前时间的置尾并按降序排序

    现在用id来代替时间这样好测试 看一下测试表数据 执行按需求规则排序的sql SELECT * FROM number_generator ORDER BY id < 16 , IF(id &l ...

  7. 【2w字干货】ArrayList与LinkedList的区别以及JDK11中的底层实现

    1 概述 本文主要讲述了ArrayList与LinkedList的相同以及不同之处,以及两者的底层实现(环境OpenJDK 11.0.10). 2 两者区别 在详细介绍两者的底层实现之前,先来简单看一 ...

  8. Spring Boot demo系列(五):Docker部署

    2021.2.24 更新 1 概述 本文讲述了如何使用Docker部署Spring Boot应用,首先介绍了Docker的安装过程,接着介绍了Docker的一些基础知识,最后讲述了Dockerfile ...

  9. mariadb_1 数据库介绍及基本操作

    数据库介绍 1.什么是数据库? 简单的说,数据库就是一个存放数据的仓库,这个仓库是按照一定的数据结构(数据结构是指数据的组织形式或数据之间的联系)来组织,存储的,我们可以通过数据库提供的多种方法来管理 ...

  10. 分布式存储bfs

    来自bilibili的bfs,很喜欢它的分层结构,我认为,把它改造成类似hadoop的平台,也是可以的. 1.实现分布式存储 其实就是同步元信息和调度的问题,同步元信息可以使用zk,调度具体看应用.b ...