自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

故障現(xiàn)場 | MQ消息亂序造成的業(yè)務(wù)事故

開發(fā) 前端
深夜,小艾接到了一通突如其來的電話,是物流系統(tǒng)的負(fù)責(zé)人曹工焦急的聲音。他火急火燎地反饋了一個嚴(yán)重的問題——大批用戶投訴物流信息異常,訂單狀態(tài)與實(shí)際情況不符,用戶已完成支付,但物流單還是待支付狀態(tài)。

1. 問題&分析

1.1. 案例

深夜,小艾接到了一通突如其來的電話,是物流系統(tǒng)的負(fù)責(zé)人曹工焦急的聲音。他火急火燎地反饋了一個嚴(yán)重的問題——大批用戶投訴物流信息異常,訂單狀態(tài)與實(shí)際情況不符,用戶已完成支付,但物流單還是待支付狀態(tài)。

小艾立刻警覺起來,意識到這個問題可能對公司的業(yè)務(wù)以及用戶體驗(yàn)造成重大影響。她一邊安撫曹工的情緒,一邊迅速啟動緊急響應(yīng)機(jī)制,通知QA對線上變更進(jìn)行回滾。

隨著回滾進(jìn)程的推進(jìn),系統(tǒng)逐步恢復(fù)正常。緊接著,他手工導(dǎo)出上線以來的全部訂單,并與曹工一起進(jìn)行數(shù)據(jù)核對,對問題數(shù)據(jù)進(jìn)行修復(fù)。終于忙完了,天空已經(jīng)微微發(fā)亮……

1.2. 問題分析

上午稍微補(bǔ)了個覺,小艾洗漱完畢后對這件事進(jìn)行分析:訂單已支付,物流單待支付。

現(xiàn)在訂單和物流的系統(tǒng)交互如下:

圖片圖片

在正常的業(yè)務(wù)流程中,訂單發(fā)布事件和物流監(jiān)聽事件緊密相連。

  • 訂單系統(tǒng)發(fā)布一個“訂單已創(chuàng)建”事件時,物流系統(tǒng)會立即響應(yīng)并在其內(nèi)部創(chuàng)建一條對應(yīng)的物流單據(jù)。
  • 當(dāng)支付環(huán)節(jié)完成并觸發(fā)“訂單已支付”事件時,物流系統(tǒng)會找到關(guān)聯(lián)的物流單據(jù)并更新其為待發(fā)貨狀態(tài)。

在正常情況下,沒有出現(xiàn)不一致的情況。小艾想到了最近的系統(tǒng)變更:

最近上線的一項(xiàng)新功能——禮品贈送。為了降低對下游系統(tǒng)的影響,小艾通過在應(yīng)用層對流程進(jìn)行編排的方式實(shí)現(xiàn)該功能,簡單來說,就是系統(tǒng)先創(chuàng)建訂單,然后模擬支付成功,這樣既能滿足禮品贈送的需求,又能保障下游契約消息沒有變化。新流程如下所示:

圖片圖片

整個流程與原來的方案沒有差別,問題究竟出現(xiàn)在哪呢?無奈的小艾只好打開 idea 查看源碼,終于發(fā)現(xiàn)問題所在:

@Service
public 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 相互獨(dú)立,根本就無法保障投遞順序。在手動支付場景下,由于用戶從訂單創(chuàng)建到支付完成通常會有 5 秒以上的延遲,在這種情況下該實(shí)現(xiàn)可以保障邏輯的執(zhí)行順序。然而在禮品贈送這個場景,系統(tǒng)先創(chuàng)建訂單,然后模擬支付成功,導(dǎo)致“訂單已創(chuàng)建”和“訂單已支付”兩個事件幾乎同時發(fā)出,在接收端就有可能先收到支付成功事件,再收到訂單已創(chuàng)建事件,從而導(dǎo)致訂單狀態(tài)和物流單狀態(tài)不一致,具體流程如下:

圖片圖片

如果順序錯了,就會導(dǎo)致業(yè)務(wù)狀態(tài)不一致:

  • 物流先接到支付成功事件,在查詢物流單時由于找不到物流單所以更新失敗。
  • 隨后物流接到訂單創(chuàng)建事件,根據(jù)邏輯創(chuàng)建一條待支付的物流單,但由于該訂單的支付成功事件在上一步已經(jīng)錯過,所以物流一直停留在待支付狀態(tài)。

問題終于找到了?。。?/p>

2. 解決方案

2.1. 方案一:主動延時

既然是順序問題,那最簡的方法就是對支付成功消息進(jìn)行延時發(fā)送。

方案如下:

圖片圖片

中間增加一個延時組件便能解決這個問題,但不同的方案影響巨大:

  • sleep 方案,會導(dǎo)致大量線程處于阻塞狀態(tài),增加接口響應(yīng)時間,同時降低系統(tǒng)的吞吐。在線上絕對不允許這種方案的出現(xiàn)!
  • 定時器方案,下單完成后,注冊一個定時調(diào)度任務(wù),時間到達(dá)時調(diào)度器將自動執(zhí)行任務(wù)。

定時器方案,核心代碼如下:

@TransactionalEventListener
public void handle(OrderPaidEvent event){
    // 創(chuàng)建Runnable任務(wù)
    Runnable task = () -> {
        rocketMQTemplate.convertAndSend("order_paid_event", event);
    };
    // 使用ScheduledExecutorService schedule方法在5秒后執(zhí)行任務(wù)
    executor.schedule(task, 5, TimeUnit.SECONDS);
}

該方案存在幾個比較嚴(yán)重的問題:

  • 全內(nèi)存操作,容易操作任務(wù)的丟失。當(dāng)遇到非優(yōu)雅關(guān)機(jī)時,內(nèi)存中的 task 就會丟失,從而導(dǎo)致業(yè)務(wù)邏輯不完整;
  • 異步執(zhí)行,容易造成錯覺。用戶完成任務(wù)提交并不代表任務(wù)肯定會成功運(yùn)行
  • 資源管理困難,如果任務(wù)量太大會大量消耗內(nèi)存資源,甚至引起整個服務(wù) OOM

2.2. 方案二:順序消息

現(xiàn)在不少 MQ 提供順序消息的支持,比如常見的 RocketMQ 提供了兩種類型的順序消息:全局順序消息和分區(qū)順序消息。

  • 全局順序消息要求所有的消息都在一個隊(duì)列上發(fā)送和消費(fèi),因此只適用于少量隊(duì)列(通常是1個隊(duì)列,否則就無法做到全局順序)。
  • 分區(qū)順序消息則允許基于(分片鍵)進(jìn)行分區(qū),相同的消息會被發(fā)送到同一隊(duì)列中,從而在每個分區(qū)內(nèi)部實(shí)現(xiàn)順序。

分區(qū)順序消息整體設(shè)計(jì)如下:

圖片圖片

核心代碼如下:

@TransactionalEventListener
public void handle(OrderCreatedEvent event){
    Long orderId = event.getOrderId();
    Message<OrderCreatedEvent> message = MessageBuilder.withPayload(event)
            .setHeader(RocketMQHeaders.KEYS, orderId) // 設(shè)置 Sharding Key,即訂單ID
            .setHeader(RocketMQHeaders.TAGS, "OrderCreatedEvent") // 設(shè)置 Tag
            .build();
    // 發(fā)送至統(tǒng)一的 order_event_topic
    rocketMQTemplate.send("order_event_topic", message);
}

@TransactionalEventListener
public void handle(OrderPaidEvent event){
    Long orderId = event.getOrderId();
    Message<OrderPaidEvent> message = MessageBuilder.withPayload(event)
            .setHeader(RocketMQHeaders.KEYS, orderId) // 設(shè)置 Sharding Key,即訂單ID
            .setHeader(RocketMQHeaders.TAGS, "OrderCreatedEvent") // 設(shè)置 Tag
            .build();
    // 發(fā)送至統(tǒng)一的 order_event_topic
    rocketMQTemplate.send("order_event_topic", message);
}

3. 示例&源碼

代碼倉庫:https://gitee.com/litao851025/learnFromBug

代碼地址:https://gitee.com/litao851025/learnFromBug/tree/master/src/main/java/com/geekhalo/demo/mq/disorder

責(zé)任編輯:武曉燕 來源: geekhalo
相關(guān)推薦

2024-03-18 09:24:12

RocketMQ消息模型分布式

2009-06-12 16:55:10

VPN客戶端故障

2024-01-29 09:22:59

死鎖線程池服務(wù)

2009-12-23 09:37:38

集線器故障

2019-01-16 09:20:42

架構(gòu)設(shè)計(jì)JVM FullGC宕機(jī)事故

2020-09-07 08:42:13

宕機(jī)業(yè)務(wù)運(yùn)維

2013-01-22 09:57:32

2017-11-09 09:06:29

流量暴增優(yōu)化

2022-11-16 08:00:00

雪花算法原理

2013-01-21 09:41:00

路由器設(shè)備故障設(shè)置參數(shù)

2009-06-14 17:18:55

ibmdwWebSphereMQ

2023-06-29 10:10:06

Rocket MQ消息中間件

2023-10-24 07:50:18

消息中間件MQ

2025-01-10 08:20:00

MQ消息架構(gòu)

2011-05-27 09:04:39

Skype宕機(jī)

2015-12-04 15:21:43

2011-04-08 15:56:02

路由器Ip

2009-07-07 17:22:34

光纖鏈路測試故障

2009-09-22 13:54:57

VMware驅(qū)動VMware后門系統(tǒng)故障

2019-08-23 12:12:49

MQ消息隊(duì)列
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號