springboot集成rabbitmq并手动注册容器实现单个queue的ack模式
原文: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模式的更多相关文章
- SpringBoot集成RabbitMQ消息队列搭建与ACK消息确认入门
1.RabbitMQ介绍 RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性.扩展性.高可用性等方面表现不俗.Rabbi ...
- SpringBoot集成rabbitmq(二)
前言 在使用rabbitmq时,我们可以通过消息持久化来解决服务器因异常崩溃而造成的消息丢失.除此之外,我们还会遇到一个问题,当消息生产者发消息发送出去后,消息到底有没有正确到达服务器呢?如果不进行特 ...
- springboot集成rabbitmq(实战)
RabbitMQ简介RabbitMQ使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现(AMQP的主要特征是面向消息.队列.路由.可靠性.安全).支持多种客户端,如:Python.Ru ...
- SpringBoot集成RabbitMQ
官方说明:http://www.rabbitmq.com/getstarted.html 什么是MQ? MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.MQ ...
- SpringBoot集成rabbitmq(一)
前言 Rabbitmq是一个开源的消息代理软件,是AMQP协议的实现.核心作用就是创建消息队列,异步发送和接收消息.通常用来在高并发中处理削峰填谷.延迟处理.解耦系统之间的强耦合.处理秒杀订单. 入 ...
- Java SpringBoot集成RabbitMq实战和总结
目录 交换器.队列.绑定的声明 关于消息序列化 同一个队列多消费类型 注解将消息和消息头注入消费者方法 关于消费者确认 关于发送者确认模式 消费消息.死信队列和RetryTemplate RPC模式的 ...
- Springboot集成RabbitMQ之MessageConvert源码解析
问题 最近在使用RabbitMq时遇到了一个问题,明明是转换成json发送到mq中的数据,消费者接收到的却是一串数字也就是byte数组,但是使用mq可视化页面查看数据却是正常的,之前在使用过程中从未遇 ...
- SpringBoot集成RabbitMQ并实现消息确认机制
原文:https://blog.csdn.net/ctwy291314/article/details/80534604 RabbitMQ安装请参照RabbitMQ应用 不啰嗦直接上代码 目录结构如下 ...
- SpringBoot集成RabbitMQ 从零到一,学会MQ异步和解耦--
RabbitMQ 概念 RabbitMQ 即一个消息队列,_主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲,消息分发的作用._RabbitMQ使用的是AMQP协议,它是一种二进制协议.默认启 ...
随机推荐
- 【linux学习笔记一】目录处理命令
一 建立目录:mkdir make directories //创建一个name的目录 mkdir name //-p 递归创建 //在没有目录a也没有目录b的情况下 直接创建 mkdir -p a/ ...
- U-Boot NFS RCE漏洞(CVE-2019-14192)
U-Boot NFS RCE漏洞(CVE-2019-14192) 原文:https://blog.semmle.com/uboot-rce-nfs-vulnerability/ 翻译:看雪翻译小组 - ...
- 雨幕——RainCurtian
今天19年10月14日,也不算是个什么特别的日子.不多能让我的这一天变得特殊的,或许就是在今天我开通了我的第一个博客吧.细想过来每一天都是那么的相似,不过是因为有了某些事情,才变得特殊起来,比如新生命 ...
- 乐字节Java继承|方法重写、super和final关键字
大家好,乐字节的小乐又来了,上一篇是:乐字节Java|JavaBean.继承与权限修饰,也是属于Java继承的,今天继续Java继承. 一. 方法的重写 父类不满足子类的要求,按需改写.注意 方法签名 ...
- Python3实现自动查询成绩(主要使用的包有Tesseract-OCR、PIL、execjs、pytesseract、BeautifulSoup)
前提:本文仅作为技术训练,不可利用技术做非法的事. 某考试的成绩查询页面如下:查询成绩需要的数据有准考证号或者身份证.考生姓名.验证码.现在使用python来实现自动查询指定人员的考试成绩(不知道准考 ...
- twemproxy配置
redis多主从,多节点,读写分离架构. nutcracker.yml的twemproxy配置 #redis_main是twemproxy所控制redis主从集群逻辑名称 redis_main: #t ...
- 019 Android 形状可绘制对象(根据要求绘制图片)+图片选择器
1.目标效果 绘制颜色渐变的图片 2.实现方法 (1)在app--->res--->drawable 右击drawable文件夹右键,new ---->drawable resour ...
- PAT(B) 1068 万绿丛中一点红(C)
题目链接:1068 万绿丛中一点红 (20 point(s)) 参考博客:1068. 万绿丛中一点红(20) i逆天耗子丶 题目描述 对于计算机而言,颜色不过是像素点对应的一个 24 位的数值.现给定 ...
- ACM集训
2019-07-18 09:06:10 emmm.... 昨天5个小时做了一道题,心情复杂,不着急慢慢来 Ivan recently bought a detective book. The book ...
- mybatis与Spring集成(Aop整合PagerAspect插件)
目的: Mybatis与spring集成 Aop整合pagehelper插件 Mybatis与spring集成 导入pom依赖 <?xml version="1.0" enc ...