RocketMQ解决幂等性问题
一.造成重复消费的原因
在于回馈机制。正常情况下,消费者在消费消息时候,消费完毕后,会发送一个ACK确认信息给消息队列(broker),消息队列(broker)就知道该消息被消费了,就会将该消息从消息队列中删除。
不同的消息队列发送的确认信息形式不同,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,kafka实际上有个offset的概念。
造成重复消费的原因?,就是因为网络原因闪断,ACK返回失败等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。(因为消息重试等机制的原因,如果一个consumer断了,rocketmq有consumer集群,会将该消息重新发给其他consumer)
这个问题针对业务场景来答,分以下三种情况:
(1)比如,你拿到这个消息做数据库的insert操作,那就容易了,给这个消息做一个唯一的主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
(2)再比如,你拿到这个消息做redis的set的操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就算幂等操作。
(3)如果上面两种情况还不行,上大招。准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis.那消费者开始消费前,先去redis中查询有没有消费记录即可。
二.单机环境解决方案
生产者:发送消息同时set一个key做唯一标识
public static void main(String[] args) throws MQClientException {
DefaultMQProducer producer = new DefaultMQProducer("rmq-group");
producer.setNamesrvAddr("192.168.42.22:9876;192.168.42.33:9876");
producer.setInstanceName("producer");
producer.start();
try {
for (int i = 0; i < 1; i++) {
Thread.sleep(1000); // 每秒发送一次MQ
Message msg = new Message("itmayiedu-topic", // topic 主题名称
"TagA", // tag 临时值
("itmayiedu-6" + i).getBytes()// body 内容
);
//setKey,做唯一标识
msg.setKeys(System.currentTimeMillis() + ""); SendResult sendResult = producer.send(msg);
System.out.println(sendResult.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
producer.shutdown();
}
消费者:
//保存标识的集合
static private Map<String, String> logMap = new HashMap<>(); public static void main(String[] args) throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("rmq-group"); consumer.setNamesrvAddr("192.168.42.22:9876;192.168.42.33:9876");
consumer.setInstanceName("consumer");
consumer.subscribe("itmayiedu-topic", "TagA"); consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
String key = null;
String msgId = null;
try {
for (MessageExt msg : msgs) {
key = msg.getKeys();
//判断集合当中有没有存在key,存在就不需要重试,不存在先存key再回来重试后消费消息
if (logMap.containsKey(key)) {
// 无需继续重试。
System.out.println("key:"+key+",无需重试...");
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
msgId = msg.getMsgId();
System.out.println("key:" + key + ",msgid:" + msgId + "---" + new String(msg.getBody()));
//模拟异常
int i = 1 / 0;
} } catch (Exception e) {
e.printStackTrace();
//重试
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
} finally {
//保存key
logMap.put(key, msgId);
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("Consumer Started.");
}
执行效果:
三.集群环境解决方案
在生产者端要保证幂等性的话,大概可以使用以下两种方式:
① RocketMQ支持消息查询的功能,只要去RocketMQ查询一下是否已经发送过该条消息就可以了,不存在则发送,存在则不发送
② 引入redis,在发送消息到RocketMQ成功之后,向redis中插入一条数据,如果发生重试,则先去redis中查询一下该条消息是否已经发送过了,存在的话就不重复发送消息了
生产者的这两种幂等性方案都可以实现,但是都存在一定的缺陷
方案①,RocketMQ消息查询的性能不是特别好,如果是在高并发的场景下,每条消息在发送到RocketMQ时都去查询一下,可能会影响接口的性能
方案②,在一些极端的场景下,redis也无法保证消息发送成功之后,就一定能写入redis成功,比如写入消息成功而redis此时宕机,那么再次查询redis判断消息是否已经发送过,是无法得到正确结果的
既然在消费者做幂等性的方案都不是特别靠谱,那就再在消费者端来做吧
消息的消费,最后都对应的是数据库的操作,只要在消息消费的时候,判断一下数据库中是否已经消费过了这条消息,就可以保证幂等性了,例如使用唯一索引,保证一条消息只被消费一次。
参考:https://blog.csdn.net/LO_YUN/article/details/104135197
去重原则:1.幂等性 2.业务去重
幂等性:(处理必须唯一) 无论这个业务请求被(consumer)执行多少次,我们的数据库的结果都是唯一的,不可变的。
去重策略:去重表机制,业务拼接去重策略(比如唯一流水号)
1.建立一个消息表,拿到这个消息做数据库的insert操作。给这个消息做一个唯一主键(primary key)或者唯一约束,那么就算出现重复消费的情况,就会导致主键冲突。
高并发下去重:采用Redis去重(key天然支持原子性并要求不可重复),但是由于不在一个事务,要求有适当的补偿策略,但是对于很重要的业务,不应该支持补偿
2.利用redis事务,主键(我们必须把全量的操作数据都存放在redis里,然后定时去和数据库做数据同步)—-即消费处理后,该处理本来应该保存在数据库的,先保存在redis,再通过一定业务方式从redis中取数据进行db持久化
3.利用redis和关系型数据库一起做去重机制
4.拿到这个消息做redis的set的操作.redis就是天然幂等性
5.准备一个第三方介质,来做消费记录。以redis为例,给消息分配一个全局id,只要消费过该消息,将 < id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。
消息重复消费是一个非常常见的问题,在很多系统调用频繁的场景下,都可能会出现超时重试的情况,还有就是系统频繁迭代,经常重启系统更新的场景,也会出现消息重复消费
生产者端发送重复的消息到RocketMQ中其实问题不大,消息只是在RocketMQ中重复了,并没有影响到系统的数据,我们只需要在最后修改数据库的时候,保证好幂等性即可
RocketMQ解决幂等性问题的更多相关文章
- 【Java面试】什么是幂等?如何解决幂等性问题?
一个在传统行业工作了7年的粉丝私信我. 他最近去很多互联网公司面试,遇到的很多技术和概念都没听过. 其中就有一道题是:"什么是幂等.如何解决幂等性问题"? 他说,这个概念听都没听过 ...
- RocketMQ 解决 No route info of this topic 异常步骤
原文地址:https://blog.csdn.net/chenaima1314/article/details/79403113 rocketmq运行时提示 No route info of this ...
- Springboot整合RocketMQ解决分布式事务
直接上代码: 代码结构如下: 依次贴出相关类: DataSource1Config: package com.example.demo.config;import org.apache.ibatis. ...
- rocketmq(三 java操作rocket API, rocketmq 幂等性)
JAVA操作rocketmq: 1.导入rocketmq所需要的依赖: <dependency> <groupId>com.alibaba.rocketmq</group ...
- Rocketmq原理&最佳实践
MQ背景&选型 消息队列作为高并发系统的核心组件之一,能够帮助业务系统解构提升开发效率和系统稳定性.主要具有以下优势: 削峰填谷(主要解决瞬时写压力大于应用服务能力导致消息丢失.系统奔溃等问题 ...
- RabbitMQ消息幂等性问题
文章目录 1. 什么是幂等性?1.1 消息队列的幂等性1.2 模拟重试机制1.2.1 生产者代码1.2.2 消费者代码1.2.3 消费者 application.yml 配置2. 如何保证消息幂等性, ...
- RocketMQ 源码分析之路由中心(NameServer)
你可能没有看过 RocketMQ 的架构图,没关系,一起来学习一下,RocketMQ 架构图如下: 在 RocketMQ 中,有四个角色: Producer:消息的生产者,每个 MQ 中间件都有. C ...
- 消息队列之事务消息,RocketMQ 和 Kafka 是如何做的?
每个时代,都不会亏待会学习的人. 大家好,我是 yes. 今天我们来谈一谈消息队列的事务消息,一说起事务相信大家都不陌生,脑海里蹦出来的就是 ACID. 通常我们理解的事务就是为了一些更新操作要么都成 ...
- MQ的面试题
MQ的优点和缺点? 优点:解耦 异步,削峰 解耦: 所以需要用来解耦: 异步: 解决方法: 削峰: 解决方法是: 缺点:降低高可用性.增加系统的复杂程度.一致性问题 降低高可用的原因:系统引入的外部依 ...
随机推荐
- Coroutine 预激装饰器
预激装饰器 讨论如何终止协程之前,我们要先谈谈如何启动协程.使用协程之前必须预激,可是这一 步容易忘记.为了避免忘记,可以在协程上使用一个特殊的装饰器.接下来介绍这样一个 装饰器. 预激协程的装饰器, ...
- Multicast
Source Specific Multicast (SSM) The multicast that you are probably familiar with (PIM sparse and de ...
- LwIP的SNMP学习笔记
关于这方面的资料网上非常少,做一下笔记. 在LwIP中,在\lwip-1.4.1\src\core\snmp目录下有SNMP相关的c文件,在lwip-1.4.1\src\include\lwip目录下 ...
- 查看php相关信息
1.最常见的就是 创建一个 php页面 ,例如 test.php, 内容如下 <?php phpinfo();?> 直接访问 这个页面,就可以看到php的 信息了 2.其它方法 直 ...
- Shell脚本 server rsync 控制脚本
[root@backup ~]# vim /etc/init.d/rsync#!/bin/bash #this script for start|stop rsync daemon service s ...
- -bash: warning: setlocale: LC_CTYPE: cannot change locale (zh_CN.UTF-8mb4): No such file or directory
前几天登录服务器发现出现了这些个警告,一直没时间去处理他,今天难得有空,处理一下并记录下来,希望可以帮助到有需要的朋友. 警告信息如下: Last login: Tue May :: from 192 ...
- vue路由--命名视图
有时候想同时(同级)展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar(侧导航) 和 main(主内容) 两个视图,这个时候命名视图就派上用场了.你可以在界面中拥有多个单独命名的视图, ...
- C#设计模式学习笔记:(13)模板方法模式
本笔记摘抄自:https://www.cnblogs.com/PatrickLiu/p/7837716.html,记录一下学习过程以备后续查用. 一.引言 今天我们要讲行为型设计模式的第一个模式--模 ...
- Linux中screen命令
screen是一款由GNU计划开发的用于命令行终端切换的自由软件.用户可以通过该软件同时连接多个本地或远程的命令行会话,并在其间自由切换.GNU Screen可以看作是窗口管理器的命令行界面版本.它提 ...
- VMware使用与安装
VMware安装 下载完Vmware -> 双击打开安装包 -> 选择下一步(如下图界面) 选择接受协议,点击下一步 选择经典进行安装.这个是默认安装,会把默认插件安装到相对应的路径 选择 ...