当前位置:首页 > 科技  > 软件

故障现场 | MQ消息乱序造成的业务事故

来源: 责编: 时间:2024-04-02 17:21:50 278观看
导读1. 问题&分析1.1. 案例深夜,小艾接到了一通突如其来的电话,是物流系统的负责人曹工焦急的声音。他火急火燎地反馈了一个严重的问题——大批用户投诉物流信息异常,订单状态与实际情况不符,用户已完成支付,但物流单还是待支

1. 问题&分析

1.1. 案例

深夜,小艾接到了一通突如其来的电话,是物流系统的负责人曹工焦急的声音。他火急火燎地反馈了一个严重的问题——大批用户投诉物流信息异常,订单状态与实际情况不符,用户已完成支付,但物流单还是待支付状态。mxe28资讯网——每日最新资讯28at.com

小艾立刻警觉起来,意识到这个问题可能对公司的业务以及用户体验造成重大影响。她一边安抚曹工的情绪,一边迅速启动紧急响应机制,通知QA对线上变更进行回滚。mxe28资讯网——每日最新资讯28at.com

随着回滚进程的推进,系统逐步恢复正常。紧接着,他手工导出上线以来的全部订单,并与曹工一起进行数据核对,对问题数据进行修复。终于忙完了,天空已经微微发亮……mxe28资讯网——每日最新资讯28at.com

1.2. 问题分析

上午稍微补了个觉,小艾洗漱完毕后对这件事进行分析:订单已支付,物流单待支付。mxe28资讯网——每日最新资讯28at.com

现在订单和物流的系统交互如下:mxe28资讯网——每日最新资讯28at.com

图片图片mxe28资讯网——每日最新资讯28at.com

在正常的业务流程中,订单发布事件和物流监听事件紧密相连。mxe28资讯网——每日最新资讯28at.com

  • 订单系统发布一个“订单已创建”事件时,物流系统会立即响应并在其内部创建一条对应的物流单据。
  • 当支付环节完成并触发“订单已支付”事件时,物流系统会找到关联的物流单据并更新其为待发货状态。

在正常情况下,没有出现不一致的情况。小艾想到了最近的系统变更:mxe28资讯网——每日最新资讯28at.com

最近上线的一项新功能——礼品赠送。为了降低对下游系统的影响,小艾通过在应用层对流程进行编排的方式实现该功能,简单来说,就是系统先创建订单,然后模拟支付成功,这样既能满足礼品赠送的需求,又能保障下游契约消息没有变化。新流程如下所示:mxe28资讯网——每日最新资讯28at.com

图片图片mxe28资讯网——每日最新资讯28at.com

整个流程与原来的方案没有差别,问题究竟出现在哪呢?无奈的小艾只好打开 idea 查看源码,终于发现问题所在:mxe28资讯网——每日最新资讯28at.com

@Servicepublic class RocketMQProducer {    @Autowired    private RocketMQTemplate rocketMQTemplate;    @TransactionalEventListener    public void handle(OrderCreatedEvent event){        rocketMQTemplate.convertAndSend("order_created_event", event);    }    @TransactionalEventListener    public void handle(OrderPaidEvent event){        rocketMQTemplate.convertAndSend("order_paid_event", event);    }}

下单和支付成功使用两个不同的 topic,两个 topic 相互独立,根本就无法保障投递顺序。在手动支付场景下,由于用户从订单创建到支付完成通常会有 5 秒以上的延迟,在这种情况下该实现可以保障逻辑的执行顺序。然而在礼品赠送这个场景,系统先创建订单,然后模拟支付成功,导致“订单已创建”和“订单已支付”两个事件几乎同时发出,在接收端就有可能先收到支付成功事件,再收到订单已创建事件,从而导致订单状态和物流单状态不一致,具体流程如下:mxe28资讯网——每日最新资讯28at.com

图片图片mxe28资讯网——每日最新资讯28at.com

如果顺序错了,就会导致业务状态不一致:mxe28资讯网——每日最新资讯28at.com

  • 物流先接到支付成功事件,在查询物流单时由于找不到物流单所以更新失败。
  • 随后物流接到订单创建事件,根据逻辑创建一条待支付的物流单,但由于该订单的支付成功事件在上一步已经错过,所以物流一直停留在待支付状态。

问题终于找到了!!!mxe28资讯网——每日最新资讯28at.com

2. 解决方案

2.1. 方案一:主动延时

既然是顺序问题,那最简的方法就是对支付成功消息进行延时发送。mxe28资讯网——每日最新资讯28at.com

方案如下:mxe28资讯网——每日最新资讯28at.com

图片图片mxe28资讯网——每日最新资讯28at.com

中间增加一个延时组件便能解决这个问题,但不同的方案影响巨大:mxe28资讯网——每日最新资讯28at.com

  • sleep 方案,会导致大量线程处于阻塞状态,增加接口响应时间,同时降低系统的吞吐。在线上绝对不允许这种方案的出现!
  • 定时器方案,下单完成后,注册一个定时调度任务,时间到达时调度器将自动执行任务。

定时器方案,核心代码如下:mxe28资讯网——每日最新资讯28at.com

@TransactionalEventListenerpublic void handle(OrderPaidEvent event){    // 创建Runnable任务    Runnable task = () -> {        rocketMQTemplate.convertAndSend("order_paid_event", event);    };    // 使用ScheduledExecutorService schedule方法在5秒后执行任务    executor.schedule(task, 5, TimeUnit.SECONDS);}

该方案存在几个比较严重的问题:mxe28资讯网——每日最新资讯28at.com

  • 全内存操作,容易操作任务的丢失。当遇到非优雅关机时,内存中的 task 就会丢失,从而导致业务逻辑不完整;
  • 异步执行,容易造成错觉。用户完成任务提交并不代表任务肯定会成功运行
  • 资源管理困难,如果任务量太大会大量消耗内存资源,甚至引起整个服务 OOM

2.2. 方案二:顺序消息

现在不少 MQ 提供顺序消息的支持,比如常见的 RocketMQ 提供了两种类型的顺序消息:全局顺序消息和分区顺序消息。mxe28资讯网——每日最新资讯28at.com

  • 全局顺序消息要求所有的消息都在一个队列上发送和消费,因此只适用于少量队列(通常是1个队列,否则就无法做到全局顺序)。
  • 分区顺序消息则允许基于(分片键)进行分区,相同的消息会被发送到同一队列中,从而在每个分区内部实现顺序。

分区顺序消息整体设计如下:mxe28资讯网——每日最新资讯28at.com

图片图片mxe28资讯网——每日最新资讯28at.com

核心代码如下:mxe28资讯网——每日最新资讯28at.com

@TransactionalEventListenerpublic void handle(OrderCreatedEvent event){    Long orderId = event.getOrderId();    Message<OrderCreatedEvent> message = MessageBuilder.withPayload(event)            .setHeader(RocketMQHeaders.KEYS, orderId) // 设置 Sharding Key,即订单ID            .setHeader(RocketMQHeaders.TAGS, "OrderCreatedEvent") // 设置 Tag            .build();    // 发送至统一的 order_event_topic    rocketMQTemplate.send("order_event_topic", message);}@TransactionalEventListenerpublic void handle(OrderPaidEvent event){    Long orderId = event.getOrderId();    Message<OrderPaidEvent> message = MessageBuilder.withPayload(event)            .setHeader(RocketMQHeaders.KEYS, orderId) // 设置 Sharding Key,即订单ID            .setHeader(RocketMQHeaders.TAGS, "OrderCreatedEvent") // 设置 Tag            .build();    // 发送至统一的 order_event_topic    rocketMQTemplate.send("order_event_topic", message);}

3. 示例&源码

代码仓库:https://gitee.com/litao851025/learnFromBugmxe28资讯网——每日最新资讯28at.com

代码地址:https://gitee.com/litao851025/learnFromBug/tree/master/src/main/java/com/geekhalo/demo/mq/disordermxe28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-80867-0.html故障现场 | MQ消息乱序造成的业务事故

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 三分钟学会消息队列实践

下一篇: 你最擅长使用哪个异步编程模式?

标签:
  • 热门焦点
  • 5月iOS设备性能榜:M1 M2依旧是榜单前五

    和上个月一样,没有新品发布的iOS设备性能榜的上榜设备并没有什么更替,仅仅只有跑分变化而产生的排名变动,刚刚开始的苹果WWDC2023,推出的产品也依旧是新款Mac Pro、新款Mac Stu
  • JavaScript 混淆及反混淆代码工具

    介绍在我们开始学习反混淆之前,我们首先要了解一下代码混淆。如果不了解代码是如何混淆的,我们可能无法成功对代码进行反混淆,尤其是使用自定义混淆器对其进行混淆时。什么是混
  • 一文看懂为苹果Vision Pro开发应用程序

    译者 | 布加迪审校 | 重楼苹果的Vision Pro是一款混合现实(MR)头戴设备。Vision Pro结合了虚拟现实(VR)和增强现实(AR)的沉浸感。其高分辨率显示屏、先进的传感器和强大的处理能力
  • 从 Pulsar Client 的原理到它的监控面板

    背景前段时间业务团队偶尔会碰到一些 Pulsar 使用的问题,比如消息阻塞不消费了、生产者消息发送缓慢等各种问题。虽然我们有个监控页面可以根据 topic 维度查看他的发送状态,
  • 2023年,我眼中的字节跳动

    此时此刻(2023年7月),字节跳动从未上市,也从未公布过任何官方的上市计划;但是这并不妨碍它成为中国最受关注的互联网公司之一。从2016-17年的抖音强势崛起,到2018年的&ldquo;头腾
  • 2天涨粉255万,又一赛道在抖音爆火

    来源:运营研究社作者 | 张知白编辑 | 杨佩汶设计 | 晏谈梦洁这个暑期,旅游赛道彻底火了:有的「地方」火了&mdash;&mdash;贵州村超旅游收入 1 个月超过 12 亿;有的「博主」火了&m
  • 华为Mate60系列模具曝光:采用硕大圆形后置相机模组+拼接配色方案

    据此前多方爆料,今年华为将开始恢复一年双旗舰战略,除上半年推出的P60系列外,往年下半年的Mate系列也将迎来更新,有望在9-10月份带来全新的华为Mate60
  • 三星Galaxy Z Fold/Flip 5国行售价曝光 :最低7499元/12999元起

    据官方此前宣布,三星将于7月26日也就是明天在韩国首尔举办Unpacked活动,届时将带来带来包括Galaxy Buds 3、Galaxy Watch 6、Galaxy Tab S9、Galaxy
  • 北京:科技教育体验基地开始登记

      北京“科技馆之城”科技教育体验基地登记和认证工作日前启动。首批北京科技教育体验基地拟于2023年全国科普日期间挂牌,后续还将开展常态化登记。  北京科技教育体验基
Top