前言

在使用rabbitmq时,我们可以通过消息持久化来解决服务器因异常崩溃而造成的消息丢失。除此之外,我们还会遇到一个问题,当消息生产者发消息发送出去后,消息到底有没有正确到达服务器呢?如果不进行特殊配置,默认情况下发送的消息是不会给生产者返回任何响应的,也就是默认情况下生产者并不知道消息是否正常到达了服务器。对于数据必达的需求,你肯定对消息的来龙去脉都有个了接,这种情况下就需要用到rabbitmq消息确认。

消息确认

rabbitmq消息确认分为生产者确认和消费者确认。

生产者消费确认提供了两种机制:

  • 通过事务机制实现
  • 通过confirm机制实现

事务机制则用到channel.txSelect、channel.txCommit、channel.txRollback。可以参考下面AMQP协议流转过程(参考Rabbitmq实战指南)

事务机制在一条消息发送之后会阻塞发送端,以等待rabbitmq回应,之后才继续发送下一条消息。所以相对来说事务机制的性能要差一些。事务机制会降低rabbitmq的吞吐量,所以又引入了另一种轻量级的方式:confirm机制。

     生产者通过调用channel.confirmSelect将信道设置为confirm模式,之后Rabbitmq会返回Confirm.Select-Ok命令表示同意生产者将当前信道设置为confirm模式。所有被发送的后续消息都被ack或nack一次。类似如下代码:

channel.confirmSelect()

channel.basicPublish("exchange","routingkey",null,"test".getBytes())

confirm机制流转过程参考下图(参考Rabbitmq实战指南)

消费者确认

消费者在订阅消息队列时指定autoAck参数。当参数设置为false时rabbitmq会等待消费者显式回复确认信号才会从内存或者磁盘种删除这条消息。参数默认为true。当autoAck设置为false时,对于rabbitmq服务器而言,队列中的消息分成了两部分:一部分是等待投递给消费者的消息、一部分是已经投递给消费者的消息但是还没有收到确认信号的消息。可通过RabbitMQ Web平台查看队列中Ready和UnAck对应的数量。

消费者消息确认涉及到3个方法:channel.basicAck、channel.basicNack、channel.basicReject

SpringBoot集成rabbitmq下实现消息确认

springboot集成rabbitmq实现消息确认主要涉及两个回调方法(ReturnCallback、ConfirmCallback)。这里消费者部分我用两种方式来实现。一种是基于SimpleMessageListenerContainer。 另一种就是用RabbitListener注解实现。

1、application.yml

spring:
rabbitmq:
host: 192.168.80.128
port: 5672
username: admin
password: admin
virtual-host: /
publisher-confirms: true
publisher-returns: true
listener:
simple:
acknowledge-mode: manual
concurrency: 1
max-concurrency: 10
retry:
enabled: true

 

2、配置文件(这里实现ReturnCallback、ConfirmCallback)

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.Nullable; @Configuration
public class MqConfig { private Logger logger= LoggerFactory.getLogger(MqConfig.class); @Autowired
RabbitTemplate rabbitTemplate; @Autowired
ConnectionFactory connectionFactory; @Bean
public Queue queue(){
return new Queue("testMq",true); //持久化队列(默认值也是true)
} @Bean
public DirectExchange directExchange(){
return new DirectExchange("testMq",true,false);
} @Bean
Binding binding(Queue queue,DirectExchange directExchange){
return BindingBuilder.bind(queue).to(directExchange).with("testMq");
} /**
* i->replyCode
* s->replyText
* s1->exchange
* s2->routingKey
* **/
//消息从交换器发送到队列失败时触发
RabbitTemplate.ReturnCallback msgReturnCallback=new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) { logger.info("消息:{},错误码:{},失败原因:{},交换器:{},路由key:{}",message.getMessageProperties().getCorrelationId(),i,s,s1,s2);
}
}; //消息发送到交换器时触发
RabbitTemplate.ConfirmCallback msgConfirmCallback=new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(@Nullable CorrelationData correlationData, boolean b, @Nullable String s) {
if(b){
logger.info("消息{}发送exchange成功",correlationData.getId());
}else{
logger.info("消息发送到exchange失败,原因:{}",s);
}
}
}; /***
* 消费者确认(方式二)
* **/
@Bean
public SimpleMessageListenerContainer listenerContainer(){
SimpleMessageListenerContainer container=new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setQueueNames("testMq");
container.setExposeListenerChannel(true);
container.setMaxConcurrentConsumers(10);
container.setConcurrentConsumers(1);
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
container.setMessageListener(new ChannelAwareMessageListener() {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
try{
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
logger.info("接收消息:{}",new String(message.getBody()));
}catch (Exception ex){ //channel.basicReject
//channel.basicNack } }
}); return container;
} /**
* 生产者的回调都在这里
* **/
@Autowired
public RabbitTemplate rabbitTemplate(){
//消息发送失败后返回到队列中
rabbitTemplate.setMandatory(true); rabbitTemplate.setReturnCallback(msgReturnCallback);
rabbitTemplate.setConfirmCallback(msgConfirmCallback); return rabbitTemplate;
} }

 另一种消费端实现方式

import com.rabbitmq.client.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component; import java.io.IOException; @Component
public class MqConsumer { private Logger logger= LoggerFactory.getLogger(MqConsumer.class);
@RabbitListener(queues = "testMq")
public void handler(Message message,Channel channel){
try {
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
logger.info("接收消息:{}",new String(message.getBody()));
} catch (IOException e) { e.printStackTrace();
}
}
}

3、消息生产者

消息发送时注意生成一个消息id。一开始没用到这个参数,在消息接收时消费者会抛空指针异常

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import java.util.UUID; @Controller
@RequestMapping("/rabbitMq")
public class MqController { private Logger logger= LoggerFactory.getLogger(MqController.class); @Autowired
RabbitTemplate rabbitTemplate; @RequestMapping("/sendMq")
@ResponseBody
public String sendMq(){ /**
* 这里exchange、routingkey都叫testMq
* **/
Object message=null;
for(int i=0;i<10;i++){
logger.info("生产者:第{}条消息",i);
CorrelationData correlationId=new CorrelationData(UUID.randomUUID().toString());
message="第"+i+"条消息";
rabbitTemplate.convertAndSend("testMq","testMq",message,correlationId);
} return "sending..."; } }

  

从运行截图中可以看到生产者和消费者都收到对应的回调消息。

SpringBoot集成rabbitmq(二)的更多相关文章

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

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

  2. springboot集成rabbitmq(实战)

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

  3. SpringBoot集成rabbitmq(一)

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

  4. SpringBoot集成RabbitMQ

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

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

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

  6. springboot集成rabbitmq并手动注册容器实现单个queue的ack模式

    原文:https://blog.csdn.net/qq_38439885/article/details/88982373 进入正题,本文会介绍两种实现rabbitmq的ack模式的方法,分别为: 一 ...

  7. springboot集成rabbitmq的一些坑

    一.默认管理页面地址是 http://127.0.0.1:15672 但是spring配置连接里面要把端口改成5672,如果不配置的话默认就是端口5672 spring.rabbitmq.host=1 ...

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

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

  9. SpringBoot整合RabbitMq(二)

           本文序列化和添加package参考:https://www.jianshu.com/p/13fd9ff0648d RabbitMq安装 [root@topcheer ~]# docker ...

随机推荐

  1. 设置yum源:

    1.企业    阿里开源镜像站:   http://mirrors.aliyun.com/ 搜狐开源镜像站: http://mirrors.sohu.com/ 网易开源镜像站: http://mirr ...

  2. Applet 应用程序进行数字签名,对系统文件进行读写操作

    转:http://www.iteye.com/topic/154531 最近在研究applet,打算使用applet来开发一个上传文件上传控件,之前因为一直觉得applet的沙箱控制导致applet不 ...

  3. EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下?

    前言 这个问题从未遇见过,是一位前辈问我EF Core内存泄漏问题时我才去深入探讨这个问题,刚开始我比较惊讶,居然还有这种问题,然后就有了本文,直接拿前辈的示例代码并稍加修改成就了此文,希望对在自学E ...

  4. 一起来学Spring Cloud | 第二章:服务注册和发现组件 (Eureka)

    本篇文章,很浅显的一步步讲解如何搭建一个能运行的springcloud项目(带所有操作截图).相信!看完本篇之后,你会觉得springcloud搭建如此简单~~~~ 一. Eureka简介: 1.1  ...

  5. 【转】AB实验设计思路及实验落地

    这篇文章会讨论: 在什么情况下需要做 AB 实验 从产品/交互角度,如何设计一个实验 前端工程师如何打点 如何统计数据,并保证数据准确可信 如何分析实验数据,有哪些数据需要重点关注 附:如何搭建前端实 ...

  6. eShopOnContainers 知多少[5]:EventBus With RabbitMQ

    1. 引言 事件总线这个概念对你来说可能很陌生,但提到观察者(发布-订阅)模式,你也许就很熟悉.事件总线是对发布-订阅模式的一种实现.它是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需 ...

  7. 兄弟俩畅游Tomcat城市的SpringMVC科技园区

    Tomcat城市 Tomcat这座城市的历史相当悠久了,经历过几次大的变迁后,呈现出非常明显的地域特征. 从城市往西走,过了城乡结合部以后,可以说是满目疮痍.一片破败,这就是Servlet地区,这座城 ...

  8. traefik 结合 docker-compose 的快速安装及使用

    traefik 介绍 traefik 是一个为了让部署微服务更加便捷而诞生的现代HTTP反向代理.负载均衡工具. 它支持多种后台 (Docker, Swarm, Kubernetes, Maratho ...

  9. asp.net core 系列之并发冲突

    本文介绍如何处理多个用户并发更新同一实体(同时)时出现的冲突 . 主要是两种:一种,检查属性并发冲突,使用 [ConcurrencyCheck] ;另一种,检测行的并发冲突,使用 rowversion ...

  10. EFCore中 join on的不同

    当 多条件 left join on 时 LEFT OUTER JOIN on new { u.UserId, ue.ExamId } equals new { sac.UserId, sac.Exa ...