业务架构设计---MQ出现消息乱序了如何解决

MQ出现消息乱序了如何解决?

消息中间件如Kafka、RocketMQ等,普通的消息是有可能存在乱序的,比如说因为网络延迟导致某个消息发送晚了,因为系统异常导致第一个消息处理失败了,等等原因都可能会导致消息乱序

举个简单的例子,一次下单过程中,有一个支付消息、一个发货消息。按理说支付一定在发货之前。所以消息的顺序也是先处理支付消息、再处理发货消息。但是对于一些特殊的业务,比如那种虚拟商品,可能支付后马上就自动发货了。这时候如果有一点点网络延迟,就可能导致发货消息优先于支付消息投递。这就是所谓的消息乱序。

一般来说,消息乱序会导致系统处理异常,比如A消息你还没出来就处理B消息的话可能会失败,也有可能你直接把B消息处理成功了,导致A再来的时候无法处理。等等一系列问题。所以这个乱序的问题是非常关键的。

一般来说,我们有几种办法来解决这个消息乱序的问题。

1、顺序消息

对于那些明确的有顺序的消息,比如像支付消息和发货消息,这个就可以在发送的时候就用顺序消息的方式发送。把他们按照顺序投递到同一个partition(队列上)上,利用分区的顺序性保证消息的顺序投递。

同时还需要确保只有一个消费者进行串行消费,这样才能完全避免消息的乱序。

2、前置状态判断

对于上面说的支付消息和发货消息,我们可以在消息体中增加一个前置状态的信息,比如beforeStatus。

作为一个消费支付消息和发货消息的系统,我们可以基于这个beforeStatus来判断和我系统中的当前状态是否一致,如果是一致的,说明我是可以处理的,那么我就处理这个消息,如果是不一致的,那说明我要的消息还没来,那我就把这个消息处理失败,让MQ下次再重投给我。

这个方案有两个要求:

  • 1、消息要能推进单据状态,比如支付消息可以把订单从待支付推进到已支付。
  • 2、消息的状态是单向的,不能出现那种从待支付推进到已支付了,过了一会又变成待支付了。

这样就能通过状态来确保消息的有序。

3、增加序列号

如果无法完全保证发送和处理的顺序,又没有状态来做前置判断,可以在消息中引入序列号,消费端根据序列号重排。例如:

  • 1.在消息中附加一个递增的序列号。
  • 2.消费端使用缓冲区缓存收到的消息,根据序列号重新排序后再处理。

缺点是会增加系统复杂度,并且需要设置缓存超时时间来处理丢失的消息。

4、自己实现排序

还有一种方案,其实是对上面第2个和第3个方案的优化。

第二个方案存在一个问题,那就是依赖MQ的重新投递,有可能会导致最终这个消息丢失了,因为一旦长时间无法消费,消息就会不再重投了。而且不断地让MQ重试,也可能会导致消息堆积,并且对系统造成一定的压力。

第三个方案的问题就是需要再内存中维护一个队列,来进行排序,太麻烦了。

那么,我们有一个做法。是这样的流程:

  • 1、接到消息之后,做基本的前置校验,如消息幂等、参数齐全等,如果校验不通过,直接返回失败。
  • 2、一旦消息校验成功,把消息体转成一个内部的事件,这个事件是自己定义的,方便后续解析和处理。
  • 3、把这个事件存到数据库中,状态设置为待处理。
  • 4、如果数据库保存成功,返回消息处理成功。
  • 5、再第四步返回之前,开启异步线程处理这个事件,执行他需要执行的代码,如果成功,则把消息状态设置为已处理。如果没成功,不用改消息状态(或者改为失败也可以),然后在执行次数上+1
  • 6、起一个异步任务,定时扫描事件表中的未成功的事件进行重试。

这么做,就能确保所有的事件我都有存储,并且存储后立刻返回,避免消息重投和堆积。消息存储下来之后,我就可以基于这些消息做排序,以及重试了,如果某个消息处理失败了,也不怕,不断重试即可。当达到了一定次数之后,报警出来人工跟进。

进一步优化

为了减少这个方案的定时任务带来的延迟,我们可以在写入消息表的时候,在redis中存一条记录,业务单号(比如订单号)当作key,然后把存入的消息的主键id当作value存进去。

这样再有消息过来的时候,先正常处理,如果处理成功了,去redis中查一下是不是存在相同业务单号的待处理的消息,有的话,根据存储的主键id查询对应的事件,放线程池中进行处理。

相关推荐
止语Lab2 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活2 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困2 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn2 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530142 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908402 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金2 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛2 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒3 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端
卷无止境3 小时前
提升 Python 代码健壮性的方法大盘点
后端·python