1 什么是消费幂等

当出现消费者对某条消息重复消费的情况时,重复消费的结果与消费一次的结果是相同的,并且多次消 费并未对业务系统产生任何负面影响,那么这个消费过程就是消费幂等的。

幂等:若某操作执行多次与执行一次对系统产生的影响是相同的,则称该操作是幂等的。

在互联网应用中,尤其在网络不稳定的情况下,消息很有可能会出现重复发送或重复消费。如果重复的 消息可能会影响业务处理,那么就应该对消息做幂等处理。

2. 消息重复的场景分析

什么情况下可能会出现消息被重复消费呢?最常见的有以下三种情况:

发送时消息重复

当一条消息已被成功发送到Broker并完成持久化,此时出现了网络闪断,从而导致Broker对Producer应 答失败。 如果此时Producer意识到消息发送失败并尝试再次发送消息,此时Broker中就可能会出现两 条内容相同并且Message ID也相同的消息,那么后续Consumer就一定会消费两次该消息。

消费时消息重复

消息已投递到Consumer并完成业务处理,当Consumer给Broker反馈应答时网络闪断,Broker没有接收 到消费成功响应。为了保证消息至少被消费一次的原则,Broker将在网络恢复后再次尝试投递之前已 被处理过的消息。此时消费者就会收到与之前处理过的内容相同、Message ID也相同的消息。

Rebalance时消息重复

当Consumer Group中的Consumer数量发生变化时,或其订阅的Topic的Queue数量发生变化时,会触 发Rebalance,此时Consumer可能会收到曾经被消费过的消息。

3. 通用解决方案

两要素

幂等解决方案的设计中涉及到两项要素:

1.幂等令牌,与唯一性处理。只要充分利用好这两要素,就可以 设计出好的幂等解决方案。 幂等令牌:是生产者和消费者两者中的既定协议,通常指具备唯⼀业务标识的字符串。例如,订 单号、流水号。一般由Producer随着消息一同发送来的。

2.唯一性处理:服务端通过采用⼀定的算法策略,保证同⼀个业务逻辑不会被重复执行成功多次。 例如,对同一笔订单的多次支付操作,只会成功一次。

解决方案,例:

对于常见的系统,幂等性操作的通用性解决方案是:

  1. 首先通过缓存去重。在缓存中如果已经存在了某幂等令牌,则说明本次操作是重复性操作;若缓 存没有命中,则进入下一步。

  2. 在唯一性处理之前,先在数据库中查询幂等令牌作为索引的数据是否存在。若存在,则说明本次 操作为重复性操作;若不存在,则进入下一步。

  3. 在同一事务中完成三项操作:唯一性处理后,将幂等令牌写入到缓存,并将幂等令牌作为唯一索 引的数据写入到DB中。

问: 第1步已经判断过是否是重复性操作了,为什么第2步还要再次判断?

能够进入第2步,说明已经不是重复操作了,第2次判断是否重复? 当然不重复。一般缓存中的数据是具有有效期的。缓存中数据的有效期一旦过期,就是发生缓存穿透,使请求直接就到达了DBMS(数据库)。

以支付场景为例:

  1. 当支付请求到达后,首先在Redis缓存中却获取key为支付流水号的缓存value。若value不空,则 说明本次支付是重复操作,业务系统直接返回调用侧重复支付标识;若value为空,则进入下一步 操作
  2. 到DBMS中根据支付流水号查询是否存在相应实例。若存在,则说明本次支付是重复操作,业务 系统直接返回调用侧重复支付标识;若不存在,则说明本次操作是首次操作,进入下一步完成唯 一性处理
  3. 在分布式事务中完成三项操作:
    • 完成支付任务
    • 将当前支付流水号作为key,任意字符串作为value,通过set(key, value, expireTime)将数 据写入到Redis缓存
    • 将当前支付流水号作为主键,与其它相关数据共同写入到DBMS

4 RocketMQ 的消费幂等的实现

RocketMQ消费幂等的解决方案很简单:为消息指定不会重复的唯一标识。

因为Message ID有可能出现重复的情 况,所以真正安全的幂等处理,不建议以Message ID作为处理依据。

最好的方式是以业务唯一标识作为 幂等处理的关键依据,而业务的唯一标识可以通过消息Key设置。

以支付场景为例,可以将消息的Key设置为订单号,作为幂等处理的依据。具体代码示例如下:

Message message = new Message();
message.setKey("ORDERID_100");
SendResult sendResult = producer.send(message);

消费者收到消息时可以根据消息的Key即订单号来实现消费幂等:

consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,ConsumeConcurrentlyContext context) {
for(MessageExt msg:msgs){
String key = msg.getKeys();
// 根据业务唯一标识Key做幂等处理
// ……
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});

RocketMQ能够保证消息不丢失,但不能保证消息不重复。

RocketMQ(8) 消费幂等的更多相关文章

  1. RocketMQ之消息幂等

    幂等(idempotent.idempotence)是一个数学与计算机学概念,常见于抽象代数中. 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同. 首先我们了解一下什么是 ...

  2. 【转】RocketMQ事务消费和顺序消费详解

    RocketMQ事务消费和顺序消费详解 转载说明:该文章纯转载,若有侵权或给原作者造成不便望告知,仅供学习参考. 一.RocketMq有3中消息类型 1.普通消费 2. 顺序消费 3.事务消费 顺序消 ...

  3. RocketMQ(7)---RocketMQ顺序消费

    RocketMQ顺序消费 如果要保证顺序消费,那么他的核心点就是:生产者有序存储.消费者有序消费. 一.概念 1.什么是无序消息 无序消息 无序消息也指普通的消息,Producer 只管发送消息,Co ...

  4. 一次 RocketMQ 顺序消费延迟的问题定位

    一次 RocketMQ 顺序消费延迟的问题定位 问题背景与现象 昨晚收到了应用报警,发现线上某个业务消费消息延迟了 54s 多(从消息发送到MQ 到被消费的间隔): 2021-06-30T23:12: ...

  5. RocketMQ - 消费者消费方式

    RocketMQ的消费方式包含Pull和Push两种 Pull方式:用户主动Pull消息,自主管理位点,可以灵活地掌控消费进度和消费速度,适合流计算.消费特别耗时等特殊的消费场景.缺点也显而易见,需要 ...

  6. RocketMQ 顺序消费只消费一次 坑

    rocketMq实现顺序消费的原理 produce在发送消息的时候,把消息发到同一个队列(queue)中,消费者注册消息监听器为MessageListenerOrderly,这样就可以保证消费端只有一 ...

  7. RocketMq顺序消费

    部分内容出处   https://www.jianshu.com/p/453c6e7ff81c rocketmq内部有4个默认的队里,在发送消息时,同一组的消息需要按照顺序,发送到相应的mq中,同一组 ...

  8. RocketMQ事务消费和顺序消费详解

    一.RocketMq有3中消息类型 1.普通消费 2. 顺序消费 3.事务消费 顺序消费场景 在网购的时候,我们需要下单,那么下单需要假如有三个顺序,第一.创建订单 ,第二:订单付款,第三:订单完成. ...

  9. 51.RocketMQ 顺序消费

    大部分的员工早上的心情可能不会很好,因为这时想到还有很多事情要做,压力会大点,一般到下午4点左右,状态会是一天中最好的,因为这时大部分的工作做得差不多了,又快要下班了,当然也不是绝对.要注意记录各下属 ...

  10. 关于RocketMQ消息消费与重平衡的一些问题探讨

    其实最好的学习方式就是互相交流,最近也有跟网友讨论了一些关于 RocketMQ 消息拉取与重平衡的问题,我姑且在这里写下我的一些总结. ## 关于 push 模式下的消息循环拉取问题 之前发表了一篇关 ...

随机推荐

  1. css 动画 div顺时针方向移动,

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. Gin 框架介绍与快速入门

    Gin 框架介绍与快速入门 目录 Gin 框架介绍与快速入门 一.Gin框架介绍 1. 快速和轻量级 2. 路由和中间件 3. JSON解析 4. 支持插件 5. Gin相关文档 二.基本使用 1.安 ...

  3. PicoPixel贴图查看器

    Pico Pixel Pico Pixel是一款纹理查看器,支持查看以下文件格式:TGA,BMP,JPG,DDS,PNG,OpenEXR, KTX, HDR, GIF, TIF. 此外,Pico Pi ...

  4. 从零开始匹配vim(1)——选项设置

    前面我们算是对 vimscript 做了一个入门,并且实现了一个 输出 hello world 的语句.现在我们继续进行 vimscript 的学习. set语句 之前在介绍 vim 基础的时候,我们 ...

  5. 【六】强化学习之DQN---PaddlePaddlle【PARL】框架{飞桨}

    相关文章: [一]飞桨paddle[GPU.CPU]安装以及环境配置+python入门教学 [二]-Parl基础命令 [三]-Notebook.&pdb.ipdb 调试 [四]-强化学习入门简 ...

  6. 《Java 面经手册》PDF,417页11.5万字,完稿!

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 我膨胀了 ,在看了大部分以面试讲解的 Java 文章后,发现很多内容的讲解都偏向于翻 ...

  7. SpringCloud-Gateway搭建保姆级教程

    一.网关介绍 1.什么是网关? 使⽤服务⽹关作为接⼝服务的统⼀代理,前端通过⽹关完成服务的统⼀调⽤ 2.⽹关可以⼲什么? 路由:接⼝服务的统⼀代理,实现前端对接⼝服务的统⼀访问 过滤:对⽤户请求进⾏拦 ...

  8. FOG Project的 FOS 编译

    FOG Project系统是一个免费的开源计算机网络克隆和管理解决方案系统,与传统的Ghost有很大的不同,如果您是计算机维护管理人员,当有大量机器需要同时部署上线的时候FOG Project是一个可 ...

  9. Java并发(十)----线程之守护线程

    默认情况下,Java 进程需要等待所有线程都运行结束,才会结束.有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束. 例: log.debug(&q ...

  10. 小知识:enable_ddl_logging参数的设置和日志位置变化

    业务部门需求,要协助客户DBA查truncate操作历史执行情况. 首先确认数据库已开启enable_ddl_logging, 然后从alert中查找没有记录: 之前11g版本都是记录到alert日志 ...