RabbitMQ一般情况很少丢失,但是不能排除意外,为了保证我们自己系统高可用,我们必须作出更好完善措施,保证系统的稳定性。

下面来介绍下,如何保证消息的绝对不丢失的问题,下面分享的绝对干货,都是在知名互联网产品的产线中使用。

1.消息持久化

2.ACK确认机制

3.设置集群镜像模式

4.消息补偿机制

一、消息持久化

RabbitMQ 的消息默认存放在内存上面,如果不特别声明设置,消息不会持久化保存到硬盘上面的,如果节点重启或者意外crash掉,消息就会丢失。

所以就要对消息进行持久化处理。如何持久化,下面具体说明下:

要想做到消息持久化,必须满足以下三个条件,缺一不可。

1)Exchange 设置持久化

2)Queue 设置持久化

3)Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息

 Exchange 设置持久化

    @Bean
TopicExchange exchange() {
return new TopicExchange("topicExchange");
}

默认的Topic交换机,持久化属性为true。

改为:

return new TopicExchange("topicExchange",false,false);  

执行下:

报错:

inequivalent arg 'durable' for exchange 'topicExchange' in vhost '/': received 'false' but current is 'true'

看下当前topic交换机属性:

好像没找到线上切换交换机持久化属性的入口,有知道的大神可以告知下,我这里是删除掉原来的而交换机,再次执行:

此时重启RabbitMq服务,未持久化的交换机消失了。。。。

Queue 设置持久化

    @Bean
public Queue queueArm() {
return new Queue(TopicRabbitConfig.ARM_QUEUE);
}

源码:

如果改为非持久化,也是先删除,再执行。非持久化后,重启服务,队列就没了。

 Message持久化发送

RabbitTemplate.class

MessageProperties.class

默认是持久化。

二、ACK确认机制

多个消费者同时收取消息,比如消息接收到一半的时候,一个消费者死掉了(逻辑复杂时间太长,超时了或者消费被停机或者网络断开链接),如何保证消息不丢?

这个使用就要使用Message acknowledgment 机制,就是消费端消费完成要通知服务端,服务端才把消息从内存删除。

这样就解决了,及时一个消费者出了问题,没有同步消息给服务端,还有其他的消费端去消费,保证了消息不丢的case。

demo:

package com.example.demo.rabbitMq.ack;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class RabbitTemplateConfig { private Logger logger = LoggerFactory.getLogger(this.getClass()); /**
* 定制化amqp模板
* ReturnCallback接口用于实现消息发送到RabbitMQ 交换器,但无相应队列与交换器绑定时的回调 即消息发送不到任何一个队列中 ack
* ConfirmCallback接口用于实现消息发送到RabbitMQ交换器后接收ack回调 即消息发送到exchange ack
*
* @return
*/
@Bean("rabbitTemplateAck")
public AmqpTemplate getabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplateAck = new RabbitTemplate(connectionFactory);
rabbitTemplateAck.setMessageConverter(new Jackson2JsonMessageConverter());
rabbitTemplateAck.setMandatory(true); //消息返回, 需要配置spring.rabbitmq.publisher-returns=true
rabbitTemplateAck.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange,
String routingKey) {
String correlationId = message.getMessageProperties().getCorrelationId();
logger.info("消息:{} 发送失败, 应答码:{} 原因:{} 交换机: {} 路由键: {}", correlationId, replyCode, replyText,
exchange, routingKey);
}
}); // 消息确认, 需要配置 spring.rabbitmq.publisher-confirms=true
rabbitTemplateAck.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
logger.info("消息发送到exchange成功");
} else {
logger.info("消息发送到exchange失败,原因: {}", cause);
}
}); return rabbitTemplateAck;
} }
   @Test
public void send4() throws Exception {
topicSender.send4();
}

测试:确认发送到交换机了:

修改发送路由,如下:

 rabbitTemplateAck.convertAndSend("topicExchange","1arm.gun",list);

再次执行,结果如下:

再看demo:

生产者新增交换机和队列

public static final String BYTE_QUEUE = "byte.queue";
@Bean
public Queue queueByte() {
return new Queue(TopicRabbitConfig.BYTE_QUEUE);
}
@Bean
Binding bindingExchangeByte(Queue queueByte, TopicExchange exchange) {
return BindingBuilder.bind(queueByte).to(exchange).with("byte.#");
}
TopicSender.java
public void send5() throws IOException {
User user = new User();
user.setUserName("Sender1.....");
user.setMobile("555555555");
byte[] body = Base64Utils.obj2byte(user);
Message message = new Message(body,new MessageProperties());
rabbitTemplateAck.convertAndSend("topicExchange","byte.message",message);
}

消费者工程:

package com.example.demo.rabbitMq.ack;

import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class RabbitTemplateConfig {
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(new Jackson2JsonMessageConverter());
return factory;
}
}
package com.example.demo.rabbitMq.exchange.topic;

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 TopicReceiver5 { private Logger logger = LoggerFactory.getLogger(this.getClass()); @RabbitListener(queues = TopicRabbitConstant.BYTE_QUEUE, containerFactory="rabbitListenerContainerFactory")
public void process(Message message, Channel channel) throws IOException {
try{
System.out.println("Receiver5 : " + message);
//int i = 6/0; }catch (Exception e){
if (!message.getMessageProperties().getRedelivered()) {
System.out.println("消息即将再次返回队列处理...");
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
} else {
//requeue为是否重新回到队列
System.out.println("消息已重复处理失败,拒绝再次接收...");
//BasicReject方法第一个参数是消息的DeliveryTag,对于每个Channel来说,每个消息都会有一个DeliveryTag
//第二个参数是是否放回queue中,requeue,如果只有一个消费者的话,true将导致无限循坏
channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
}
}
}
}

执行:

如果人造异常,将会走黄色部分,最后因为重复请求而拒绝再次放入队列。

三、设置集群镜像模式

我们先来介绍下RabbitMQ三种部署模式:

1)单节点模式:最简单的情况,非集群模式,节点挂了,消息就不能用了。业务可能瘫痪,只能等待。
2)普通模式:默认的集群模式,某个节点挂了,该节点上的消息不能用,有影响的业务瘫痪,只能等待节点恢复重启可用(必须持久化消息情况下)。
3)镜像模式:把需要的队列做成镜像队列,存在于多个节点,属于RabbitMQ的HA方案

为什么设置镜像模式集群,因为队列的内容仅仅存在某一个节点上面,不会存在所有节点上面,所有节点仅仅存放消息结构和元数据。下面自己画了一张图介绍普通集群丢失消息情况:

如果想解决上面途中问题,保证消息不丢失,需要采用HA 镜像模式队列。

下面介绍下三种HA策略模式:

1)同步至所有的
2)同步最多N个机器
3)只同步至符合指定名称的nodes

命令处理HA策略模版:rabbitmqctl set_policy [-p Vhost] Name Pattern Definition [Priority]

1)为每个以“rock.wechat”开头的队列设置所有节点的镜像,并且设置为自动同步模式
rabbitmqctl set_policy ha-all "^rock.wechat" '{"ha-mode":"all","ha-sync-mode":"automatic"}'
rabbitmqctl set_policy -p rock ha-all "^rock.wechat" '{"ha-mode":"all","ha-sync-mode":"automatic"}'

2)为每个以“rock.wechat.”开头的队列设置两个节点的镜像,并且设置为自动同步模式
rabbitmqctl set_policy -p rock ha-exacly "^rock.wechat" \
'{"ha-mode":"exactly","ha-params":2,"ha-sync-mode":"automatic"}'

3)为每个以“node.”开头的队列分配指定的节点做镜像
rabbitmqctl set_policy ha-nodes "^nodes\." \
'{"ha-mode":"nodes","ha-params":["rabbit@nodeA", "rabbit@nodeB"]}'

但是:HA 镜像队列有一个很大的缺点就是:   系统的吞吐量会有所下降

四、消息补偿机制

为什么还要消息补偿机制呢?难道消息还会丢失,没错,系统是在一个复杂的环境,不要想的太简单了,虽然以上的三种方案,基本可以保证消息的高可用不丢失的问题,

但是作为有追求的程序员来讲,要绝对保证我的系统的稳定性,有一种危机意识。

比如:持久化的消息,保存到硬盘过程中,当前队列节点挂了,存储节点硬盘又坏了,消息丢了,怎么办?

产线网络环境太复杂,所以不知数太多,消息补偿机制需要建立在消息要写入DB日志,发送日志,接受日志,两者的状态必须记录。

然后根据DB日志记录check 消息发送消费是否成功,不成功,进行消息补偿措施,重新发送消息处理。

参考:

https://www.cnblogs.com/flyrock/p/8859203.html

https://www.cnblogs.com/milicool/p/9662447.html

RabbitMq(6) 如何保证消息不丢包的更多相关文章

  1. 《RabbitMQ》如何保证消息的可靠性

    一条消费成功被消费经历了生产者->MQ->消费者,因此在这三个步骤中都有可能造成消息丢失. 一 消息生产者没有把消息成功发送到MQ 1.1 事务机制 AMQP协议提供了事务机制,在投递消息 ...

  2. 《RabbitMQ》如何保证消息不被重复消费

    一 重复消息 为什么会出现消息重复?消息重复的原因有两个:1.生产时消息重复,2.消费时消息重复. 1.1 生产时消息重复 由于生产者发送消息给MQ,在MQ确认的时候出现了网络波动,生产者没有收到确认 ...

  3. RabbitMQ消息丢失问题和保证消息可靠性-消费端不丢消息和HA(二)

    继续上篇文章解决RabbitMQ消息丢失问题和保证消息可靠性(一) 未完成部分,我们聊聊MQ Server端的高可用和消费端如何保证消息不丢的问题? 回归上篇的内容,我们知道消息从生产端到服务端,为了 ...

  4. RabbitMQ 如何保证消息不丢失?

    RabbitMQ一般情况很少丢失,但是不能排除意外,为了保证我们自己系统高可用,我们必须作出更好完善措施,保证系统的稳定性. 下面来介绍下,如何保证消息的绝对不丢失的问题,下面分享的绝对干货,都是在知 ...

  5. 如何保证mq不丢消息

    1.消息的发送流程 一条消息从生产到被消费,将会经历3个阶段 生产阶段,Producer 新建消息,然后通过网络将消息投递给MQ Broker 存储阶段,消息将会存储在Broker端磁盘中 消费阶段, ...

  6. IP视频通信中的"丢包恢复技术”(LPR)

    转自:http://blog.csdn.net/blade2001/article/details/9094709 在IP视频通话中,即使是在丢包率很小的情况下也会对使用效果造成较为明显的影响.正是由 ...

  7. 解决RabbitMQ消息丢失问题和保证消息可靠性(一)

    原文链接(作者一个人):https://juejin.im/post/5d468591f265da03b810427e 工作中经常用到消息中间件来解决系统间的解耦问题或者高并发消峰问题,但是消息的可靠 ...

  8. springboot + rabbitmq发送邮件(保证消息100%投递成功并被消费)

    前言: RabbitMQ相关知识请参考: https://www.jianshu.com/p/cc3d2017e7b3 Linux安装RabbitMQ请参考: https://www.jianshu. ...

  9. (转载)springboot + rabbitmq发送邮件(保证消息100%投递成功并被消费)

    转载自https://www.jianshu.com/p/dca01aad6bc8 一.先扔一张图   image.png 说明: 本文涵盖了关于RabbitMQ很多方面的知识点, 如: 消息发送确认 ...

随机推荐

  1. Intro to Mongoid

    Mongoid: object-document-mapper(ODM) Mongoid Configuration: rails g mongoid:config Document: Documen ...

  2. 【Python全栈-JavaScript】JavaScript的window.onload()与jQuery 的ready()的区别

    JavaScript的window.onload()与jQuery 的ready()的区别 做web开发时常用Jquery中$(document).ready()和JavaScript中的window ...

  3. 阻塞队列(BlockingQueue)

    阻塞队列是 java.util.concurrent 包提供的一个类,该类提供了多线程中通过队列实现安全高效的数据处理的功能. 所谓阻塞队列,是在普通队列基础上实现了阻塞线程的功能: 队列为空时,获取 ...

  4. python基础(3)-pycharm安装&for循环&format字符串&list列表&set集合使用

    安装IDE(PyCharm)&破解 点击下载安装包,安装. 点击下载破解补丁. 进入pycharm安装bin目录,将破解补丁copy到当前目录. 在bin目录下找到pycharm.exe.vm ...

  5. python框架之Django(12)-认证系统之auth模块

    我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统.此时我们需要实现包括用户注册.用户登录.用户认证.注销.修改密码等功能,这还真是个麻烦的事情呢. Django作为一个完美主义者的终极框 ...

  6. SybaseIQ上SQL基本使用

    锁定: sp_iqlocks 踢人: 存储过程查找: sybase central里面查询的命令(查找过滤器例如:%table%),Sybase Central/Sybase IQ 15/服务器/xx ...

  7. pprint

    pprint = pretty printer 经常用来打印 字典.json 打印出的格式会是较为标准的格式 目的:方便调试,查看中间结果,因为觉得设断点调试相对麻烦. [运行环境:macOS 10. ...

  8. 图片居中table-cell

  9. puppeteer(二)操作实例——新Web自动化工具更轻巧更简单

    一.入门实例 了解puppeteer见上一篇文章: https://www.cnblogs.com/baihuitestsoftware/p/9957343.html 1)本例主要是启动浏览器 con ...

  10. Select input 两个元素的宽度高度跟设定值不一致的问题

    在做登录框的时候,需要一个select 元素作为账号输入,一个input作为密码输入框.在css 文件中,将这两个元素的position 设置为relative ,并且width 设置为100%.刷新 ...