RabbitMQ消息幂等性问题

文章目录

      • 1. 什么是幂等性?
        • 1.1 消息队列的幂等性
        • 1.2 模拟重试机制
          • 1.2.1 生产者代码
          • 1.2.2 消费者代码
          • 1.2.3 消费者 application.yml 配置
      • 2. 如何保证消息幂等性,不被重复消费?
        • 解决方法

1. 什么是幂等性?

在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。


HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。幂等性属于语义范畴,正如编译器只能帮助检查语法错误一样,HTTP规范也没有办法通过消息格式等语法手段来定义它。

简之:一个请求,不管重复来多少次,结果是不会改变的。

1.1 消息队列的幂等性

如同HTTP方法的幂等性,消息队列同样会出现幂等性问题。

消费者在消费 MQ 中的消息时,MQ 已把消息发送给消费者,消费者在给 MQ 返回 ack 时网络中断,故 MQ 未收到确认信息,该条消息会重新发给其他的消费者,或者在网络重连后再次发送给该消费者,但实际上该消费者已成功消费了该条消息,造成消费者消费了重复的消息;注意,RabbitMQ 这种消息重试(补偿)机制是默认的。

所以,MQ 消费者的幂等性问题,主要在于 MQ 的重试机制,因为网络原因或客户端延迟消费导致重复消费。

那么,如何合适选择重试机制?我们来看两种情况。

情况1: 消费者获取到消息后,调用第三方接口,但接口暂时无法访问,是否需要重试?

需要重试

情况2: 消费者获取到消息后,抛出数据转换异常,是否需要重试?

不需要重试

总结:对于情况2,如果消费者代码抛出异常是需要发布新版本才能解决的问题,那么不需要重试,重试也无济于事。应该采用日志记录+定时任务 job 健康检查+人工进行补偿

1.2 模拟重试机制

我们采用一种短信消费者客户端异常的情况来模拟 RabbitMQ 的重试机制。

@RabbitListener(queues = "fanout_sms_queue")
public void process(String msg) {System.out.println("短信消费者获取生产者消息msg:" + msg);int i = 1/0;
}

如上代码,很显然会报错,一担报错生产者的消息时不会被消费的?

@RabbitListener 底层使用 AOP 进行异常通知拦截,如果程序没有抛出异常信息,那么就会自动提交事务;如果 AOP 异常通知拦截有捕获到异常信息的话,就会自动实现重试(补偿)机制,同时,这个补偿机制的消息会缓存到 RabbitMQ 服务器端进行存放,一直重试到不抛出异常为止。

1.2.1 生产者代码
@Component
public class FanoutProducer {@Autowiredprivate AmqpTemplate amqpTemplate;/*** 发送消息** @param queueName 队列名称*/public void send(String queueName) {String msg = "my_fanout_msg:" + System.currentTimeMillis();Message message = MessageBuilder.withBody(msg.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8").setMessageId(UUID.randomUUID() + "").build();System.out.println(msg + ":" + msg);amqpTemplate.convertAndSend(queueName, message);}
}
1.2.2 消费者代码
@Component
public class FanoutEamilConsumer {@RabbitListener(queues = "fanout_eamil_queue")public void process(Message message) throws Exception {String revMessage = Thread.currentThread().getName() + ",邮件消费者获取生产者消息msg:" + new String(message.getBody(), "UTF-8")+ ",messageId:" + message.getMessageProperties().getMessageId();System.out.println(revMessage);}
}
1.2.3 消费者 application.yml 配置
spring:rabbitmq:####连接地址host: 127.0.0.1####端口号   port: 5672####账号 username: guest####密码  password: guest### 地址virtual-host: /admin_hostlistener:simple:retry:####开启消费者重试enabled: true####最大重试次数max-attempts: 5####重试间隔次数initial-interval: 3000server:port: 8081

我们通过 RabbitMQ 配置,增加了 RabbitMQ 重试时间以及重试次数限制,在一定程度上解决了重复消费的问题,接下来看一道常问的面试题。

2. 如何保证消息幂等性,不被重复消费?

其实,这个问题也算是 MQ 面试当中经常考察的一点,因为无论是什么 MQ 都会有这个问题。

首先通过上边我们了解了什么是“幂等性”,以及 MQ 幂等性问题的产生,所以我们要清楚为什么会出现重复性消费?在什么场景会出现重复消费?

解决方法

使用全局 MessageID 判断消费方使用同一个,解决幂等性问题。
或者使用业务逻辑保证唯一(比如订单号码)

生产者关键代码:

@Autowired
private AmqpTemplate amqpTemplate;/*** 发送消息** @param queueName 队列名称*/
public void send(String queueName) {String msg = "my_fanout_msg:" + System.currentTimeMillis();Message message = MessageBuilder.withBody(msg.getBytes()).setContentType(MessageProperties.CONTENT_TYPE_JSON).setContentEncoding("utf-8").setMessageId(UUID.randomUUID() + "").build();System.out.println(msg + ":" + msg);amqpTemplate.convertAndSend(queueName, message);
}

如上,生产者在发送消息时(convertAndSend),给消息对象设置了唯一的 MessageID,只有该 MessageID 没有被消费者标记方能在重试机制中再次被消费。

消费者关键代码:

@RabbitListener(queues = "fanout_eamil_queue")
public void process(Message message) throws Exception {String revMessage = Thread.currentThread().getName()+ ",邮件消费者获取生产者消息msg:"+ new String(message.getBody(), "UTF-8")+ ",messageId:" + message.getMessageProperties().getMessageId();System.out.println(revMessage);发送邮件的逻辑XXX
}

如上,通过 message.getMessageProperties().getMessageId() 获取 MessageID,获取的 MessageID 可以用来判断是否已经被消费者消费过了,如果已经消费则取消再次消费。

通常怎么判断呢?

比如上方是一个邮件发送的消费者,在做补偿时,假如上一步邮件发送成功了,我们会把该 ID 存至 redis中,下次再调用时,先去 redis 判断是否存在该 ID 了,如果存在表明已经消费过了则直接返回,不再消费,否则消费,然后将记录存至 redis。

我创建了一个java相关的公众号,用来记录自己的学习之路,感兴趣的小伙伴可以关注一下微信公众号哈:niceyoo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/414176.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JAVA面向对象明星类

public class _01Celebrity{//属性public String name;public int age;public double height;public char gender;//构造器public _01Celebrity(String name,int age,double height,char gender){this.name name;this.age age;this.height height;this.gender gender;}//方…

工作287:命名报错

return:{data:{account_id: ,BindData: [],RomoteData:[],dialogVisible: false,ff_account_index: ,form:{},}},这种命名报错

Centos安装JDK(java环境)

王小私下问我 centos 中 jdk 怎么安装呀,所以再次整理了这篇基础环境搭建的文章。 1、创建java目录2、下载上传jdk3、解压jdk4、配置环境变量 1、创建java目录 首先我们创建java的安装目录 cd /usrmkdir javacd java 2、下载上传jdk 我们如上在 usr 目录下创建了 ja…

iOS用workspace和cocoapods管理多个项目

工作空间下多工程共享cocoapods第三方库的方法 引自 https://www.jianshu.com/p/e3cfae830985转载于:https://www.cnblogs.com/-WML-/p/8946370.html

工作288:根据时间戳处理接口

<template><div class"table-list-page"><div class"query-area"><el-date-pickerv-model"value1"type"daterange"range-separator"至"start-placeholder"开始日期"end-placeholder"结…

Centos7安装MySQL(多图)

文章目录一、在线安装1、替换网易yum源2、清理缓存3、下载rpm文件4、安装MySQL数据库二、本地安装1、上传MySQL安装包2、安装依赖的程序包3、卸载mariadb程序包4、安装MySQL程序包5、修改MySQL目录权限6、初始化MySQL三、启动MySQL1.1、在线安装方式启动MySQL1.2、本地安装方式…

ROS与Arduino学习(六)Logging日志

ROS与Arduino学习&#xff08;六&#xff09;Logging日志 Tutorial Level:客户端与服务器 Next Tutorial&#xff1a;小案例节点通信 本节较为简单告诉大家如何向系统发布日志信息。 Tips 1 日志信息发布 节点提供了五种日志消息&#xff0c;分别是debug、information、warn、…

工作289:js取整

只保留整数部分&#xff08;丢弃小数部分&#xff09; parseInt(5.1234); // 5 向下取整&#xff08;< 该数值的最大整数&#xff0c;和parseInt()一样) Math.floor(5.1234); // 5 向上取整&#xff08;有小数&#xff0c;整数部分就1&#xff09; Math.ceil(5…

读书笔记8-浪潮之巅(part3)

浪潮之巅 ——风险投资 《浪潮之巅》的前半部分列举了在现代史上举足轻重的几家大型科技公司的历史&#xff0c;虽说成功的公司各有各的绝招&#xff0c;但是读多之后又略显重复、无聊&#xff08;这不是说原书的内容、描述是无聊的&#xff0c;相反其中的每一篇都值得多次欣赏…

工作290:js日期操作

Js获取当前日期时间及其它操作 var myDate new Date(); myDate.getYear(); //获取当前年份(2位) myDate.getFullYear(); //获取完整的年份(4位,1970-????) myDate.getMonth(); //获取当前月份(0-11,0代表1月) myDate.getDate(); //获取当前日(1-31…

lower_case_table_names=1 启动报错 mysql8.0

本文为采集文章&#xff0c;主博客地址&#xff1a;https://www.cnblogs.com/niceyoo 我们知道在 Linux 环境下默认是区分大小写的&#xff0c;所以我们需要改变这种默认方式&#xff0c;经过网上各种搜索后&#xff0c;基本就是清一色的修改 lower_case_table_names&#xff0…

微信小程序 weui 使用方法

https://github.com/Tencent/weui-wxss/ 下载地址用于小程序的https://github.com/Tencent/weui 下载地址用于H5 运用示例在下载文件的文件夹 weui-wxss-master\dist\example目录下小程序全局用法在app.wxss用import "weui.wxss"转载于:https://www.cnblogs.com/ch…

工作291:当前账号是否绑定操作

<template><el-dialogopen"open"title"绑定分发平台账号":visible.sync"dialogVisible"width"40%":before-close"handleClose"><el-form style"display: flex;justify-content: center;align-items: ce…

Redis之客户端连接方式

Reis的客户端连接方式有如下几种&#xff1a; 1.基本方式 /*** 简单基本方式调用*/Testpublic void test1JedisStandardClient() {Jedis jedis new Jedis("192.168.56.101", 6379);jedis.set("123", "first line is null");String valueString …

工作292:修改父子组件传值错误

[Vue warn]: Missing required prop: “title” 在写vue项目中&#xff0c;在子组件中通过props传值的时候&#xff0c;在父组件中没有定义的话就会看到类似的报错&#xff0c; 这个意思是calendar这个组件中通过props传递一个title属性给父组件&#xff0c;并且title属性是必…

MacOS下IDEA设置智能提示不区分大小写

本文只针对&#xff0c;IDEA-2019.2.3版本 目录地址&#xff1a; Edit -> General -> Code Completion -> Match case -> 勾选去掉 截图如下&#xff1a;

工作293:新的打印操作

getAction("/task/" id "/release").then(res > {console.log(res, 8888)if (res.code 404) {this.$message({message: res.msg,type: error});this.dialogVisible false;}

博客园文章方块背景格式

有小伙伴问到方格背景的问题&#xff0c;所以写一篇文章记录我的博客园文章背景是如何制作的。 一、辅助网站1. 一键排版2. 代码主题3. 复制二、 图床设置 一、辅助网站 辅助网址&#xff1a;Md2All 作者提供了一篇帮助文章&#xff1a;玩转公众号Markdown 其实大致看完辅助网址…

day02 pycharm 安装

pycharm 是一款现在比较主流的辅助开发软件 不选择虚拟 所以选择Existing现有的 安装后只需打开当前窗口 默认的 不需要大家新的窗口 使用鼠标滚轮来实现放大缩小 使用debug模式测试代码 转载于:https://www.cnblogs.com/zhaohongyu6688/p/8962253.html

工作294:for[item.key]使用

<el-form label-width"80px"><el-form-item label"项目名"><el-input v-model"project_name" placeholder"请输入项目名"></el-input></el-form-item><el-form-item v-for"item in AweMepro&qu…