目录

业务架构设计---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查询对应的事件,放线程池中进行处理。

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
007php0077 分钟前
使用 Docker 安装 Elastic Stack 并重置本地密码
大数据·运维·后端·mysql·docker·eureka·jenkins
嘵奇8 分钟前
Spring Boot 断点续传实战:大文件上传不再怕网络中断
java·spring boot·后端
勇哥java实战分享1 小时前
聊聊四种实时通信技术:长轮询、短轮询、WebSocket 和 SSE
后端
pwzs1 小时前
掌握常见 HTTP 方法:GET、POST、PUT 到 CONNECT 全面梳理
java·后端·http
IT可乐1 小时前
人人都可以做个满血版的Manus智能体了
后端
像风一样自由20201 小时前
RESTful API工具和框架详解
后端·restful
草捏子2 小时前
接口幂等性设计:6种解决方法让重复请求不再成为系统隐患
后端
Captaincc2 小时前
AI coding的隐藏王者,悄悄融了2亿美金
前端·后端·ai编程
盖世英雄酱581362 小时前
同事说缓存都用redis啊,数据不会丢失!真的吗?
redis·后端·面试
L2ncE3 小时前
双非计算机自救指南(找工作版)
后端·面试·程序员