原文:https://blog.csdn.net/qq_38439885/article/details/88982373

进入正题,本文会介绍两种实现rabbitmq的ack模式的方法,分别为:

一、通过配置文件配置。

二、通过手动注册 SimpleMessageListenerContainer容器实现。

先介绍方法一:
通过配置文件配置。
此类实现起来较为方便,通过springboot的配置文件以及注解的形式即可完成。

1.首先引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.编写配置文件
# rabbitmq基本配置
spring.rabbitmq.host=***
spring.rabbitmq.port=5672
spring.rabbitmq.username=***
spring.rabbitmq.password=***
spring.rabbitmq.virtual-host=/

# 开启发送确认
spring.rabbitmq.publisher-confirms=true
# 开启发送失败退回
spring.rabbitmq.publisher-returns=true
# 全局开启ACK
spring.rabbitmq.listener.simple.acknowledge-mode=manual
在配置文件中使用

spring.rabbitmq.listener.simple.acknowledge-mode
来配置ack模式,这个配置有三种配置方式,分别为NONE、MANUAL、AUTO。

I:NONE:默认为NONE,也就是自动ack模式,在消费者接受到消息后无需手动ack,消费者会自动将消息ack掉。

II:MANUAL:即为手动ack模式,消费者在接收到消息后需要手动ack消息,不然消息将一直处于uncheck状态,在应用下次启动的时候会再次对消息进行消费。使用该配置需要注意的是,配置开启后即项目全局开启手动ack模式,所有的消费者都需要在消费信息后手动ack消息,否则在重启应用的时候将会有大量的消息无法被消费掉而重复消费。

III:AUTO:自动确认ack 如果此时消费者抛出异常,不同的异常会有不同的处理方式。

3.编写MQConfig的代码,实现相应的queue和exchange的注册以及绑定。
/**
* ACK 测试
*/
public static final String ACK_QUEUE_A = "ack.test.queue.A";
public static final String ACK_QUEUE_B = "ack.test.queue.B";
public static final String ACK_EXCHANGE = "ack.test.exchange";

/**
* ACK TEST
*/
@Bean
public Queue ackQueueA() {
return new Queue(ACK_QUEUE_A);
}

@Bean
public Queue ackQueueB() {
return new Queue(ACK_QUEUE_B);
}

@Bean
public FanoutExchange ackFanoutExchange() {
return new FanoutExchange(ACK_EXCHANGE);
}

@Bean
public Binding ackBindingA() {
return BindingBuilder.bind(ackQueueA()).to(ackFanoutExchange());
}

@Bean
public Binding ackBindingB() {
return BindingBuilder.bind(ackQueueB()).to(ackFanoutExchange());
}
上方代码中做了三件事:

I.注册了两个queue,分别为ackQueueA以及ackQueueB。

II.注册了一个fanout类型的exchange。

III.将两个queue和其绑定。

4. 生产者代码编写
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author hsw
* @since 9:26 2019/4/2
*/
@Slf4j
@Service
public class MQAckSender {

@Autowired
private RabbitTemplate rabbitTemplate;

public void ackMQSender(String msg) {
log.info("send ack message :" + msg);
// 生产者发送消息到exchange后没有绑定的queue时将消息退回
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
log.info("ackMQSender 发送消息被退回" + exchange + routingKey);
});
// 生产者发送消息confirm检测
this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (!ack) {
log.info("ackMQSender 消息发送失败" + cause + correlationData.toString());
} else {
log.info("ackMQSender 消息发送成功 ");
}
});
this.rabbitTemplate.convertAndSend(MQConfig.ACK_EXCHANGE, "", msg);
}

}
这里使用了RabbitTemplate而没有使用AmqpTemplate,可以将RabbitTemplate看作一个实现了AmqpTemplate的工具类,其中定义了更多方法供开发者使用。

在第一步的配置文件中定义了MANUAL的ack模式的同时,也配置了发送确认以及发送失败退回,所以在上述生产者代码中,分别配置了这两项。具体回调时间见注释。

5.消费者代码编写
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import java.io.IOException;

/**
* @author hsw
* @since 9:39 2019/4/2
*/
@Slf4j
@Service
public class MQAckReceive {

@RabbitListener(queues = MQConfig.ACK_QUEUE_A)
public void process(String msg, Channel channel, Message message) throws IOException {
log.info("ACK_QUEUE_A 收到 : " + msg);
try {
// 框架容器,是否开启手动ack按照框架配置
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
log.info("ACK_QUEUE_A 接受信息成功");
} catch (Exception e) {
e.printStackTrace();
//丢弃这条消息
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
log.info("ACK_QUEUE_A 接受信息异常");
}

}

@RabbitListener(queues = MQConfig.ACK_QUEUE_B)
public void process2(String msg, Channel channel, Message message) throws IOException {
log.info("ACK_QUEUE_B 收到 : " + msg);
try {
//告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了 否则消息服务器以为这条消息没处理掉 重启应用后还会在发
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
log.info("ACK_QUEUE_B 接受信息成功");
} catch (Exception e) {
e.printStackTrace();
//丢弃这条消息
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
log.info("ACK_QUEUE_B 接受信息异常");
}

}
}
上述代码定义了两个消费者,即为之前定义的ackQueueA以及ackQueueB的消费者。

与默认ack模式的消费者不同的是,在消费者消费信息的时候,需要手动ack掉信息,即为上述代码中的:

channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
该方法有两个参数,分别为long类型和boolean类型:

/**
* Acknowledge one or several received
* messages. Supply the deliveryTag from the {@link com.rabbitmq.client.AMQP.Basic.GetOk}
* or {@link com.rabbitmq.client.AMQP.Basic.Deliver} method
* containing the received message being acknowledged.
* @see com.rabbitmq.client.AMQP.Basic.Ack
* @param deliveryTag the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
* @param multiple true to acknowledge all messages up to and
* including the supplied delivery tag; false to acknowledge just
* the supplied delivery tag.
* @throws java.io.IOException if an error is encountered
*/
void basicAck(long deliveryTag, boolean multiple) throws IOException;
第一个deliveryTag参数为每条信息带有的tag值,第二个multiple参数为布尔类型,为true时会将小于等于此次tag的所有消息都确认掉,如果为false则只确认当前tag的信息,可根据实际情况进行选择。

再看下另外两个拒绝消息的函数:

channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
第一个方法 basicNack 有三个参数,分别为long类型、boolean类型和boolean类型:

/**
* Reject one or several received messages.
*
* Supply the <code>deliveryTag</code> from the {@link com.rabbitmq.client.AMQP.Basic.GetOk}
* or {@link com.rabbitmq.client.AMQP.Basic.GetOk} method containing the message to be rejected.
* @see com.rabbitmq.client.AMQP.Basic.Nack
* @param deliveryTag the tag from the received {@link com.rabbitmq.client.AMQP.Basic.GetOk} or {@link com.rabbitmq.client.AMQP.Basic.Deliver}
* @param multiple true to reject all messages up to and including
* the supplied delivery tag; false to reject just the supplied
* delivery tag.
* @param requeue true if the rejected message(s) should be requeued rather
* than discarded/dead-lettered
* @throws java.io.IOException if an error is encountered
*/
void basicNack(long deliveryTag, boolean multiple, boolean requeue)
throws IOException;
前两个参数和接受方法 basicAck 的参数相似,第一个deliveryTag参数为每条信息带有的tag值,第二个multiple参数为true时会将小于等于此次tag的所有消息都拒绝掉,如果为false则只拒绝当前tag的信息,可根据实际情况进行选择。

第三个参数为requeue,为true的时候会将消息重新发送到当前队列。可根据具体业务需求中不同的异常捕捉实现不同的拒绝方式。

第二个方法 basicReject 和 basicAck 方法类似,但是只能拒绝/重发当前tag的信息。

6.项目测试
@GetMapping("/ack")
public void springAck() {
try {
mqAckSender.ackMQSender("this is a ack msg");
} catch (Exception e) {
e.printStackTrace();
}
}
调用接口后返回:

2019-04-03 10:18:07.018 INFO 7352 --- [nio-8081-exec-3] c.h.a.rabbitmq.amqp.MQAckSender : send ack message :this is a ack msg
2019-04-03 10:18:07.028 INFO 7352 --- [cTaskExecutor-9] c.h.a.rabbitmq.amqp.MQAckReceive : ACK_QUEUE_B 收到 : this is a ack msg
2019-04-03 10:18:07.028 INFO 7352 --- [cTaskExecutor-9] c.h.a.rabbitmq.amqp.MQAckReceive : ACK_QUEUE_B 接受信息成功
2019-04-03 10:18:07.028 INFO 7352 --- [cTaskExecutor-1] c.h.a.rabbitmq.amqp.MQAckReceive : ACK_QUEUE_A 收到 : this is a ack msg
2019-04-03 10:18:07.028 INFO 7352 --- [cTaskExecutor-1] c.h.a.rabbitmq.amqp.MQAckReceive : ACK_QUEUE_A 接受信息成功
2019-04-03 10:18:07.035 INFO 7352 --- [2.20.4.100:5672] c.h.a.rabbitmq.amqp.MQAckSender : ackMQSender 消息发送成功
若在queueA消费者ack消息前打上断点,可在rabbitmq管理后台看到:

第一种方式的手动ack模式开启成功!

接下来介绍方法二:
通过手动注册 SimpleMessageListenerContainer容器实现。
方法一通过注解方式开启ack模式固然方便,但通过注解方式开启后,项目全局的ack模式都将被修改,那怎么样做到只修改单个消费者的ack模式呢?这里就需要手动注册相应容器来修改ack模式。话不多说,先上代码。

MQConfig和MQSender端代码不变。
@Bean
public SimpleMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames(ACK_QUEUE_A);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
container.setMessageListener((ChannelAwareMessageListener) (message, channel) -> {
log.info(ACK_QUEUE_A + "get msg:" +new String(message.getBody()));
if(message.getMessageProperties().getHeaders().get("error") == null){
// 消息手动ack
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
log.info("消息确认");
}else {
// 消息重新回到队列
//channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
// 拒绝消息(删除)
channel.basicReject(message.getMessageProperties().getDeliveryTag(),false);
log.info("消息拒绝");
}

});
return container;
}
与第一种方法的不同点:

1、配置文件配置的ack模式不会影响。

2、消费者需要配置在setMessageListener中。

上述代码中,手动注册了一个SimpleMessageListenerContainer容器,并将对应的queueName、需要修改的ack模式以及消费者收到消息后的处理一并注入到spring中。

由于是手动注册容器,不受到配置文件的影响,所以可以实现对单个queue的ack模式修改。

需要注意的是,如果消费者依旧使用@RabbitListener注解进行消费信息,手动注册容器中修改的ack模式是无效的。

---------------------
作者:hhsway
来源:CSDN
原文:https://blog.csdn.net/qq_38439885/article/details/88982373
版权声明:本文为博主原创文章,转载请附上博文链接!

springboot集成rabbitmq并手动注册容器实现单个queue的ack模式的更多相关文章

  1. SpringBoot集成RabbitMQ消息队列搭建与ACK消息确认入门

    1.RabbitMQ介绍 RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性.扩展性.高可用性等方面表现不俗.Rabbi ...

  2. SpringBoot集成rabbitmq(二)

    前言 在使用rabbitmq时,我们可以通过消息持久化来解决服务器因异常崩溃而造成的消息丢失.除此之外,我们还会遇到一个问题,当消息生产者发消息发送出去后,消息到底有没有正确到达服务器呢?如果不进行特 ...

  3. springboot集成rabbitmq(实战)

    RabbitMQ简介RabbitMQ使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现(AMQP的主要特征是面向消息.队列.路由.可靠性.安全).支持多种客户端,如:Python.Ru ...

  4. SpringBoot集成RabbitMQ

    官方说明:http://www.rabbitmq.com/getstarted.html 什么是MQ? MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.MQ ...

  5. SpringBoot集成rabbitmq(一)

    前言 Rabbitmq是一个开源的消息代理软件,是AMQP协议的实现.核心作用就是创建消息队列,异步发送和接收消息.通常用来在高并发中处理削峰填谷.延迟处理.解耦系统之间的强耦合.处理秒杀订单.  入 ...

  6. Java SpringBoot集成RabbitMq实战和总结

    目录 交换器.队列.绑定的声明 关于消息序列化 同一个队列多消费类型 注解将消息和消息头注入消费者方法 关于消费者确认 关于发送者确认模式 消费消息.死信队列和RetryTemplate RPC模式的 ...

  7. Springboot集成RabbitMQ之MessageConvert源码解析

    问题 最近在使用RabbitMq时遇到了一个问题,明明是转换成json发送到mq中的数据,消费者接收到的却是一串数字也就是byte数组,但是使用mq可视化页面查看数据却是正常的,之前在使用过程中从未遇 ...

  8. SpringBoot集成RabbitMQ并实现消息确认机制

    原文:https://blog.csdn.net/ctwy291314/article/details/80534604 RabbitMQ安装请参照RabbitMQ应用 不啰嗦直接上代码 目录结构如下 ...

  9. SpringBoot集成RabbitMQ 从零到一,学会MQ异步和解耦--

    RabbitMQ 概念 RabbitMQ 即一个消息队列,_主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲,消息分发的作用._RabbitMQ使用的是AMQP协议,它是一种二进制协议.默认启 ...

随机推荐

  1. js实现深度优先遍历和广度优先遍历

    深度优先遍历和广度优先遍历 什么是深度优先和广度优先 其实简单来说 深度优先就是自上而下的遍历搜索 广度优先则是逐层遍历, 如下图所示 1.深度优先 2.广度优先 两者的区别 对于算法来说 无非就是时 ...

  2. linux中硬盘分区、格式化、挂载

    已经接触了小半年的linux,基本命令用的还行,就是涉及到深入操作,就显得不够看了,比如linux中的硬盘操作,于是整理了这篇博客. 1. 主分区,扩展分区,逻辑分区的联系和区别 ​ 一个硬盘可以有1 ...

  3. Nginx负载均衡总结2

    如果要支持健康检查需要开启health_check(好吧,这个是nginx plus版本才有的功能,plus是付费版) 还有一点,Windows的nginx不支持udp等协议,所以有一些测试还必须用l ...

  4. LeetCode 637. 二叉树的层平均值(Average of Levels in Binary Tree)

    637. 二叉树的层平均值 637. Average of Levels in Binary Tree LeetCode637. Average of Levels in Binary Tree 题目 ...

  5. JSP的部分知识(一)

    通过Servlet进行整个网站的开发是可以的. 不过在Servlet中输出html代码,特别是稍微复杂一点的html代码,就会给人一种很酸爽的感觉. 如果能够直接使用Html代码,然后在html中写j ...

  6. Python--递归函数实现:多维嵌套字典数据无限遍历

    原创:多层嵌套字典无限遍历,实现当value值以特殊字符$开头,并且等于某项值时,用随机函数替换该参数 """处理前的字典{'patient': {'avatarPic' ...

  7. WUSTOJ 1335: Similar Word(Java)

    题目链接:1335: Similar Word Description It was a crummy day for Lur. He failed to pass to the CET-6 (Col ...

  8. UOJ #7 NOI2014购票(点分治+cdq分治+斜率优化+动态规划)

    重写一遍很久以前写过的题. 考虑链上的问题.容易想到设f[i]为i到1的最少购票费用,转移有f[i]=min{f[j]+(dep[i]-dep[j])*p[i]+q[i]} (dep[i]-dep[j ...

  9. js中__proto__和prototype的区别和关系?(转)

    转自知乎:https://www.zhihu.com/question/34183746

  10. 优先队列问题 get it !!

    首先 队列的基本用法 头文件 #include<queue> priority_queue < int/string/struct> q//  q为队列的名字 基本操作 q.p ...