消息队列高手课
李玥
京东零售技术架构部资深架构师
立即订阅
8426 人已学习
课程目录
已完结 41 讲
0/4登录后,你可以任选4讲全文学习。
课前必读 (2讲)
开篇词 | 优秀的程序员,你的技术栈中不能只有“增删改查”
免费
预习 | 怎样更好地学习这门课?
基础篇 (8讲)
01 | 为什么需要消息队列?
02 | 该如何选择消息队列?
03 | 消息模型:主题和队列有什么区别?
04 | 如何利用事务消息实现分布式事务?
05 | 如何确保消息不会丢失?
06 | 如何处理消费过程中的重复消息?
07 | 消息积压了该如何处理?
08 | 答疑解惑(一) : 网关如何接收服务端的秒杀结果?
进阶篇 (21讲)
09 | 学习开源代码该如何入手?
10 | 如何使用异步设计提升系统性能?
11 | 如何实现高性能的异步网络传输?
12 | 序列化与反序列化:如何通过网络传输结构化的数据?
13 | 传输协议:应用程序之间对话的语言
14 | 内存管理:如何避免内存溢出和频繁的垃圾回收?
加餐 | JMQ的Broker是如何异步处理消息的?
15 | Kafka如何实现高性能IO?
16 | 缓存策略:如何使用缓存来减少磁盘IO?
17 | 如何正确使用锁保护共享数据,协调异步线程?
18 | 如何用硬件同步原语(CAS)替代锁?
19 | 数据压缩:时间换空间的游戏
20 | RocketMQ Producer源码分析:消息生产的实现过程
21 | Kafka Consumer源码分析:消息消费的实现过程
22 | Kafka和RocketMQ的消息复制实现的差异点在哪?
23 | RocketMQ客户端如何在集群中找到正确的节点?
24 | Kafka的协调服务ZooKeeper:实现分布式系统的“瑞士军刀”
25 | RocketMQ与Kafka中如何实现事务?
26 | MQTT协议:如何支持海量的在线IoT设备?
27 | Pulsar的存储计算分离设计:全新的消息队列设计思路
28 | 答疑解惑(二):我的100元哪儿去了?
案例篇 (7讲)
29 | 流计算与消息(一):通过Flink理解流计算的原理
30 | 流计算与消息(二):在流计算中使用Kafka链接计算任务
31 | 动手实现一个简单的RPC框架(一):原理和程序的结构
32 | 动手实现一个简单的RPC框架(二):通信与序列化
33 | 动手实现一个简单的RPC框架(三):客户端
34 | 动手实现一个简单的RPC框架(四):服务端
35 | 答疑解惑(三):主流消息队列都是如何存储消息的?
测试篇 (2讲)
期中测试丨10个消息队列热点问题自测
免费
期末测试 | 消息队列100分试卷等你来挑战!
结束语 (1讲)
结束语 | 程序员如何构建知识体系?
消息队列高手课
登录|注册

06 | 如何处理消费过程中的重复消息?

李玥 2019-08-03
你好,我是李玥。上节课我们讲了如何确保消息不会丢失,课后我给你留了一个思考题,如果消息重复了怎么办?这节课,我们就来聊一聊如何处理重复消息的问题。
在消息传递过程中,如果出现传递失败的情况,发送方会执行重试,重试的过程中就有可能会产生重复的消息。对使用消息队列的业务系统来说,如果没有对重复消息进行处理,就有可能会导致系统的数据出现错误。
比如说,一个消费订单消息,统计下单金额的微服务,如果没有正确处理重复消息,那就会出现重复统计,导致统计结果错误。
你可能会问,如果消息队列本身能保证消息不重复,那应用程序的实现不就简单了?那有没有消息队列能保证消息不重复呢?

消息重复的情况必然存在

在 MQTT 协议中,给出了三种传递消息时能够提供的服务质量标准,这三种服务质量从低到高依次是:
At most once: 至多一次。消息在传递时,最多会被送达一次。换一个说法就是,没什么消息可靠性保证,允许丢消息。一般都是一些对消息可靠性要求不太高的监控场景使用,比如每分钟上报一次机房温度数据,可以接受数据少量丢失。
At least once: 至少一次。消息在传递时,至少会被送达一次。也就是说,不允许丢消息,但是允许有少量重复消息出现。
Exactly once:恰好一次。消息在传递时,只会被送达一次,不允许丢失也不允许重复,这个是最高的等级。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《消息队列高手课》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(71)

  • 微微一笑
    解决一个问题,往往会引发别的问题。若消息队列实现了exactly once,会引发的问题有:①消费端在pull消息时,需要检测此消息是否被消费,这个检测机制无疑会拉低消息消费的速度。可以预想到,随着消息的剧增,消费性能势必会急剧下降,导致消息积压;②检查机制还需要业务端去配合实现,若一条消息长时间未返回ack,消息队列需要去回调看下消费结果(这个类似于事物消息的回查机制)。这样就会增加业务端的压力,与很多的未知因素。
    所以,消息队列不实现exactly once,而是at least once + 幂等性,这个幂等性让给我们去处理。

    作者回复: 👍👍👍

    2019-08-03
    55
  • oscarwin
    我觉得最重要的原因是消息队列即使做到了Exactly once级别,consumer也还是要做幂等。因为在consumer从消息队列取消息这里,如果consumer消费成功,但是ack失败,consumer还是会取到重复的消息,所以消息队列花大力气做成Exactly once并不能解决业务侧消息重复的问题。

    作者回复: 👍👍👍

    2019-08-03
    2
    40
  • 谢清
    exactly once,实现有性能损耗,并发高时易出现消息堆积;消息队列设计初衷是解决解耦,而解耦的对象往往是高并发,对性能要求较高的,从产品需求层面讲,消息队列设计更注重性能,而非精准(exactly once);基础架构角度来说,关注点是占比大的需求(不能不发,可以重发),占比极小的需求(敏感型,只能触发一次)可以单独抽出来另外实现。最后,请教老师有没有比较具体的业务场景,非用这种exactly once不可的
    2019-08-05
    7
  • linqw
    学习完如何处理消费过程中的重复消息,写下自己的理解,老师有空帮忙看下哦
    1、使用数据库的唯一索引防止消息被重复消费,感觉如果业务系统存在分库分表,消费消息被路由到不同的库或表,还是会存在问题。
    2、为更新的数据设置前置条件,可以在消息中附带属性,比如当前账户的总金额,或者表中多加一个版本号字段,配合数据库行锁,类似乐观锁的概念,Java CAS,比较内存中的旧值是否和预先的旧值相等,如果是替换成新值。存在的问题和1类似。
    3、记录并检查操作,在每个消息中维护一个全局唯一的ID,根据全局唯一ID进行判断消息是否已经被消费。存在的问题,全局唯一ID的实现有一定的复杂度,需要确保检查消费状态、更新数据、以及更新消费状态三个操作原子性,解决方式涉及到分布式锁和分布式事务,并且对高性能、高并发也有一定的影响。
    4、尝试回答下课后习题①设置成Exactly once从消息队列的角度来看,为了确保消息没有被丢失或者重复,队列需采取一定的类似回查的手段,检测消费者是否有收到消息进行处理,在一定程度上会导致队列堆积等一系列问题,并且队列实现的复杂度上升。②从消费者的角度而言,因为消费者端和Broker Service端都是会各自集群,消费者端可能会存在网络抖动,导致Broker Service为了确保消息不丢失和重复,需要一直进行回查类似的操作,但是由于网络问题,导致队列堆积。
    5、有个疑问如果队列的实现是At least once,但是为了确保消息不丢失,Broker Service会进行一定的重试,但是不可能一直重试,如果一直重试失败怎么处理了?

    作者回复:
    第一个问题,一般来说分库分表也不会有问题,为什么?因为,使用我们的方法,对于一条具体的消息,总是会落到确定的某个库表上,它的重复消息也会落地同样的库表上,所以分库分表不是问题。

    第五个问题,有的消息队列会有一个特殊的队列来保存这些总是消费失败的“坏消息”,然后继续消费之后的消息,避免坏消息卡死队列。这种坏消息一般不会是因为网络原因或者消费者死掉导致的,大多都是消息数据本身有问题,消费者的业务逻辑处理不了导致的。

    2019-08-03
    4
    7
  • leslie
    对于老师说的为何都是支持At least once:是不是与以下几种情况相关;不对之处还望老师指出,因为我是刚好最近有时会有些异常数据联想到的也算是学习此课的初衷之一。
        1.硬件异常或者系统异常导致的数据丢失:这里想咨询老师一下,消息队列为何不能做成像数据库一样的用undo log和redo log去避免硬件的这种异常。
        2.就像为何网络协议中一样TCP和UDP的区别:消息反馈可能不是每一个反馈一次,有时是一批反馈异常,传输中可能会出现丢包或者顺序不一致。
              最近几个刚好同时在学:刘超老师的网络协议、操作系统以及您的消息队列觉得之间有彼此的关系;能力有限,故而仅仅是猜测,只能通过不断的向各位老师学习才能不断的找出问题提升自己,不足之处还望老师提点-谢谢。

    作者回复: A1:主要是出于性能考虑。

    A2:大部分消息队列在实现的时候,都是批量收发的,但是,采用基于位置的确认机制,是可以保证顺序的。

    2019-08-03
    1
    5
  • 年年
    这课买的太值了,是本平台最吸引我的一门课,一口气看了八篇

    作者回复: 感谢支持!

    2019-08-14
    2
    4
  • a、
    因为目前消息队列,在发送消息给客户端的时候,一般需要客户端ack之后才能确定,这条消息是不是真的被消费了。
    1.如果客户端设置的是自动ack,那么mq就能保证只发送一次,但是这样会因为客户端消费消息不成功,而导致消息丢失
    2.如果客户端都设置手动ack,这样又有一个问题,如果mq发送消息给客户端成功了,客户端也已经消费完成了,就在准备ack的时候,和mq失去了联系,这时候mq是不知道,这条消息是否真的被消费了,只能选择重发消息。
    所以我觉得:如果消息队列保证了只发一次,那么消息队列就无法保证消息由于客户端消费失败而不丢失,就好像分布式系统中的cap理论,只能保证其中的两种,而无法三个都保证。

    作者回复: 架构设计就是在取舍之间选择最合适的实现方式。

    2019-08-03
    1
    4
  • Dovelol
    老师好,想问下关于幂等的情况,像设置帐户余额为100元,或者给余额为500的加100,如果有中间状态的变更或者ABA问题,也能算是幂等操作吗?

    作者回复: 确实这个例子解决不了ABA问题,如果要解决这个问题,只能使用版本号的方式。

    2019-08-07
    2
  • Better me
    对于思考题,三种服务质量标准都有各自的使用场景,这就好比数据库中隔离级别的实现,Serializable隔离级别不仅可以避免脏读、不可重复读,还避免了幻读,但同时代价花费也是最高,性能很低。文中的Exactly once(恰好一次)基本类似,虽然一定程度上可以避免消息重复以及消息丢失,但其实现必然也意味着高代价、低性能。最后深深的感受到架构的设计的关键就是判断和取舍,以及针对特定场景去做特定的实现。

    作者回复: 👍👍👍

    2019-08-03
    2
  • 游弋云端
    我的理解如下:
    1、按照您给的公式:At least once + 幂等消费 = Exactly once,所以对于消息队列来讲,要做到Exactly once,其实是需消费端的共同配合(幂等消费)才可完成,消息队列基本只提供At least once的实现;
    2、从给的几种幂等消费的方案看,需要引入数据库、条件更新、分布式事务或锁等额外辅助,消息队列如果需要保障Exactly once,会导致消费端代码侵入,例如需要消费端增加消息队列用来处理幂等的client端,而消费端的形态可是太多了,兼容适配工作量巨大。故这个Exactly once留给用户自己处理,并且具有选择权,毕竟不是所有业务场景都需要Exactly once,例如老师讲的机房温度上报的案例。

    作者回复: 👍👍👍

    2019-08-03
    2
  • nightmare
    kafka就算用事务,也不能保证没有重复消费,它有可能发生rebalance时,消费了数据没有提交
    2019-08-03
    1
    2
  • 张三丰
    文中有句话想跟老师确认下,如下:

    ”t0 时刻:Consumer A 收到条消息,检查消息执行状态,发现消息未处理过,开始执行“账户增加 100 元”;

    t1 时刻:Consumer B 收到条消息,检查消息执行状态,发现消息未处理过,因为这个时刻,Consumer A 还未来得及更新消息执行状态。”

    1.这是因为每个队列配置多个消费组导致的吧?
    2.通常情况下配置多个消费组是为了提升消费能力?
    3.如果配置多个消费组是为了提升消费能力,那么为什么每个消费组配置多个消费者?反正每个消费组只有一个消费者能成功消费到消息。每个消费组只配置一个消费者不行吗?

    作者回复: 这个情况不需要配置多个消费组,只要主题中配置了多个分区,同一个消费组内也会出现这种情况。

    2019-10-21
    1
  • Switch
    简而言之,大部分消息队列没必要做那么高的服务质量。
    控制级别更高的Exactly once,必须要在入队的时候做检查操作,会极大的降低消息队列的性能。除此之外,大部分场景下,Exactly once级别的服务质量足够了,如果不够,还是可以通过一些业务手段规避。
    2019-08-08
    1
  • 冰激凌的眼泪
    无处不在的check&set
    2019-08-06
    1
  • Leon📷
    之前有个止盈止损的股票平仓问题,通过消息队列发送给平仓服务去平仓,当时还没考虑到重复平仓问题,现在看来可以用全局uuid来防止这个问题,因为平仓服务是单点的,所以不用考虑分布式系统的难题,不过如果后面平行扩展了就要考虑分布式事务了
    2019-08-05
    1
  • David Mao
    请教一下老师,重复消息多的话可能会影响效能,消息队列有这方面的设计考量吗?

    作者回复: 这个还是需要使用消息队列的用户来考虑。

    2019-08-03
    1
  • 苏州睿威博科技有限公司
    针对kafka,可以在业务中记录partition +offset来判断是否幂等吗?我看网络上很少有这样的方案,是因为和业务强关联的原因吗?望解答,谢谢🙏
    2019-12-10
  • 攀攀
    kafka接收线上mysql的binlog日志来进行数据同步 在分布式消费的情况下 可能出现分布式多台机器同时消费相同数据的问题
    如果说kafka保证了每个分区的消息顺序 那么我们只要按照主id进行hash 就可以保证相同的id 只会发往相同的机器 老师 这么说对吗

    作者回复: 正确!

    2019-11-20
  • james
    第三种方案,b也会收到同样消息的原因是啥,是a消费时间太长重发导致的吗? kafka中每个consumer一个queue不会出现这种场景,而rocketmq的顺序消费也不会出现

    作者回复: 正常情况下不会出现,如果有故障,比如broker故障、consumer故障或者网络连接抖动,都可能会出现重复消费,也就是一个消息被不同的consumer都消费到的情况,kafka和rocketmq都有可能会出现这种情况。

    2019-11-10
  • 饭粒
    请教老师:
    1)利用前置条件防重复消费那块,如果当前消息没有被消费过,但是有中间账户有其他操作(转账、消费)使得账户的余额不再是500,这样消息会被错误的判重?
    2)全局 ID 查询消费状态的分布式问题,查询用 select * from t where id=8 for update 给这条消息加锁可以吗?

    作者回复: 1.这个问题已经不是消息队列的问题了,而是二个不同的系统同时更新一份数据的问题,一般都不会这么来设计。

    2. 这种方式是可行的。

    2019-11-02
收起评论
71
返回
顶部