前言

在使用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. BZOJ_2693_jzptab_莫比乌斯反演

    BZOJ_2693_jzptab_莫比乌斯反演 Description Input 一个正整数T表示数据组数 接下来T行 每行两个正整数 表示N.M Output T行 每行一个整数 表示第i组数据的 ...

  2. html中 submit和button的区别?

    前者是向数据库提交表单 后者是单纯的按钮功能

  3. jdbc 增删改查以及遇见的 数据库报错Can't get hostname for your address如何解决

    最近开始复习以前学过的JDBC今天肝了一晚上 来睡睡回笼觉,长话短说 我们现在开始. 我们先写一个获取数据库连接的jdbc封装类 以后可以用 如果不是maven环境的话在src文件下新建一个db.pr ...

  4. EFCore动态切换Schema

    最近做个分库分表项目,用到schema的切换感觉还是有些坑的,在此分享下. 先简要说下我们的分库分表 分库分表规则 我定的规则是,订单号(数字)除以16,得出的结果为这个订单所在的数据库,然后他的余数 ...

  5. 【转】大白话讲解Promise(一)

    原文地址:https://www.cnblogs.com/lvdabao/p/es6-promise-1.html ES6 Promise 先拉出来遛遛 复杂的概念先不讲,我们先简单粗暴地把Promi ...

  6. Flutter 即学即用系列博客——01 环境搭建

    前言 工欲善其事,必先利其器 所以第一篇我们来说说 Flutter 环境的搭建. 笔者这边使用的是 MAC 电脑,因此以 MAC 电脑的环境搭建为例. Windows 或者 Linux 也是类似的操作 ...

  7. NSTimer循环引用的几种解决方案

    前言 在iOS中,NSTimer的使用是非常频繁的,但是NSTimer在使用中需要注意,避免循环引用的问题.之前经常这样写: - (void)setupTimer { self.timer = [NS ...

  8. June 29th. 2018, Week 26th. Friday

    Real love is always worth waiting for. 真爱永远值得等待. From Westworld. Real love is rare, but it does exis ...

  9. WEB框架-Django框架学习-关联管理器(RelatedManager)

    一.class RelatedManager "关联管理器"是在一对多或者多对多的关联上下文中使用的管理器.它存在于下面两种情况: 1.一对多 ForeignKey关系的“另一边” ...

  10. 逆向-攻防世界-crackme

    查壳,nSpack壳,直接用软件脱壳,IDA载入程序. 很明显,就是将402130的数据和输入的数据进行异或,判断是否等于402150处的数据.dwrd占4字节. 这道题主要记录一下刚学到的,直接在I ...