日韩欧美国产精品免费一二-日韩欧美国产精品亚洲二区-日韩欧美国产精品专区-日韩欧美国产另-日韩欧美国产免费看-日韩欧美国产免费看清风阁

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發文檔 其他文檔  
 
網站管理員

電商平臺中訂單未支付過期如何實現自動關單?

freeflydom
2025年4月8日 9:10 本文熱度 277

日常開發中,我們經常遇到這種業務場景,如:外賣訂單超 30 分鐘未支付,則自動取訂單;用戶注冊成功 15 分鐘后,發短信息通知用戶等等。這就是延時任務處理場景。

在電商,支付等系統中,一設都是先創建訂單(支付單),再給用戶一定的時間進行支付,如果沒有按時支付的話,就需要把之前的訂單(支付單)取消掉。這種類以的場景有很多,還有比如到期自動收貨,超時自動退款,下單后自動發送短信等等都是類似的業務問題。

定時任務(數據庫輪詢)

通過定時任務關閉訂單,是一種成本很低,實現也很容易的方案。通過簡單的幾行代碼,寫一個定時任務,定期掃描數據庫中的訂單,如果時間過期,就將其狀態更新為關閉即可。

@Scheduled(cron = "0 0 22 * * ?")
public void pmNotify() {
    this.pmService.todoNotify();
}

優點:實現容易,成本低,基本不依賴其他組件。

缺點:

  • 時間可能不夠精確。由于定時任務掃描的間隔是固定的,所以可能造成一些訂單已經過期了一段時間才被掃描到,訂單關閉的時間比正常時間晚一些。
  • 增加了數據庫的壓力。隨著訂單的數量越來越多,掃描的成本也會越來越大,執行時間也會被拉長,可能導致某些應該被關閉的訂單遲遲沒有被關閉。

總結:采用定時任務的方案比較適合對時間要求不是很敏感,并且數據量不太多的業務場景。

JDK 延遲隊列 DelayQueue

DelayQueue是JDK提供的一個無界隊列,DelayQueue隊列中的元素需要實現Delayed,它只提供了一個方法,就是獲取過期時間。

用戶的訂單生成以后,設置過期時間比如30分鐘,放入定義好的DelayQueue,然后創建一個線程,在線程中通過while(true)不斷的從DelayQueue中獲取過期的數據。

優點:不依賴任何第三方組件,連數據庫也不需要了,實現起來也方便。

缺點:

  • 因為DelayQueue是一個無界隊列,如果放入的訂單過多,會造成JVMOOM。
  • DelayQueue基于JVM內存,如果JVM重啟了,那所有數據就丟失了。
  • 創建了一個不斷while(true)的線程,占用了cpu資源

總結:DelayQueue適用于數據量較小,且丟失也不影響主業務的場景,比如內部系統的一些非重要通知,就算丟失,也不會有太大影響。

redis過期監聽

redis 是一個高性能的KV 數據庫,除了用作緩存以外,其實還提供了過期監聽的功能。在redis.conf 中,配置notify-keyspace-events Ex 即可開啟此功能。然后在代碼中繼承KeyspaceEventMessageListener,實現onMessage 就可以監聽過期的數據量。

public abstract class KeyspaceEventMessageListener implements MessageListener, InitializingBean, DisposableBean {
    private static final Topic TOPIC_ALL_KEYEVENTS = new PatternTopic("__keyevent@*");
    //...省略部分代碼
    public void init() {
        if (StringUtils.hasText(keyspaceNotificationsConfigParameter)) {
            RedisConnection connection =
                listenerContainer.getConnectionFactory().getConnection();
            try {
                Properties config = connection.getConfig("notify-keyspace-events");
                if (!StringUtils.hasText(config.getProperty("notify-keyspace-events"))) {
                    connection.setConfig("notify-keyspace-events",
                        keyspaceNotificationsConfigParameter);
                }
            } finally {
                connection.close();
            }
        }
        doRegister(listenerContainer);
    }
    protected void doRegister(RedisMessageListenerContainer container) {
        listenerContainer.addMessageListener(this, TOPIC_ALL_KEYEVENTS);
    }
    
    //...省略部分代碼
    @Override
    public void afterPropertiesSet() throws Exception {
        init();
    }
}

通過以上源碼,我們可以發現,其本質也是注冊一個listener,利用redis 的發布訂閱,當key 過期時,發布過期消息(key)到Channel :keyevent@*:expired 中。

在實際的業務中,我們可以將訂單的過期時間設置比如30 分鐘,然后放入到redis。30 分鐘之后,就可以消費這個key,然后做一些業務上的后置動作,比如檢查用戶是否支付。

優點: 由于redis 的高性能,所以我們在設置key,或者消費key 時,速度上是可以保證的。

缺點:致命缺陷,不宜使用

在 Redis 官方手冊的keyspace-notifications: timing-of-expired-events中明確指出:

Basically expired events are generated when the Redis server deletes the key and not when the time to live theoretically reaches the value of zero

redis 自動過期的實現方式是:定時任務離線掃描并刪除部分過期鍵;在訪問鍵時惰性檢查是否過期并刪除過期鍵。也就是說,由于redis 的key 過期策略原因,當一個key 過期時,redis 從未保證會在設定的過期時間立即刪除并發送過期通知,自然我們的監聽事件也無法第一時間消費到這個key,所以會存在一定的延遲。實際上,過期通知晚于設定的過期時間數分鐘的情況也比較常見。

另外,在redis5.0 之前,訂閱發布中的消息并沒有被持久化,自然也沒有所謂的確認機制。所以一旦消費消息的過程中我們的客戶端發生了宕機,這條消息就徹底丟失了。

總結:redis 的過期訂閱相比于其他方案沒有太大的優勢,在實際生產環境中,用得相對較少。

Redisson分布式延遲隊列

Redisson是一個基于redis實現的Java駐內存數據網格,它不僅提供了一系列的分布式的Java常用對象,還提供了許多分布式服務。

Redisson除了提供我們常用的分布式鎖外,還提供了一個分布式延遲隊列RDelayedQueue,他是一種基于zset結構實現的延遲隊列,其實現類是RedissonDelayedQueue。delayqueue 中有一個名為 timeoutSetName 的有序集合,其中元素的 score 為投遞時間戳。delayqueue 會定時使用 zrangebyscore 掃描已到投遞時間的消息,然后把它們移動到就緒消息列表中。

delayqueue 保證 redis 不崩潰的情況下不會丟失消息,在沒有更好的解決方案時不妨一試。

優點:使用簡單,并且其實現類中大量使用lua 腳本保證其原子性,不會有并發重復問題。

缺點:需要依賴redis(如果這算一種缺點的話)。

總結:Redisson 是redis 官方推薦的JAVA 客戶端,提供了很多常用的功能,使用簡單、高效,推薦使用。

RocketMQ 延遲消息

延遲消息:當消息寫入到Broker 后,不會立刻被消費者消費,需要等待指定的時長后才可被消費處理的消息,稱為延時消息。

在訂單創建之后,我們就可以把訂單作為一條消息投遞到rocketmq,并將延遲時間設置為30 分鐘,這樣,30 分鐘后我們定義的consumer 就可以消費到這條消息,然后檢查用戶是否支付了這個訂單。

通過延遲消息,我們就可以將業務解耦,極大地簡化我們的代碼邏輯。

優點:可以使代碼邏輯清晰,系統之間完全解耦,只需關注生產及消費消息即可。另外其吞吐量極高,最多可以支撐萬億級的數據量。

缺點:相對來說,mq 是重量級的組件,引入mq 之后,隨之而來的消息丟失、冪等性問題等都加深了系統的復雜度。

總結:通過mq 進行系統業務解耦,以及對系統性能削峰填谷已經是當前高性能系統的標配。

RabbitMQ 死信隊列

除了RocketMQ 的延遲隊列,RabbitMQ 的死信隊列也可以實現消息延遲功能。

死信(Dead Letter) 是 rabbitmq 提供的一種機制。當一條消息滿足下列條件之一那么它會成為死信:

  • 消息被否定確認(如channel.basicNack) 并且此時requeue 屬性被設置為false。
  • 消息在隊列的存活時間超過設置的TTL時間
  • 消息隊列的消息數量已經超過最大隊列長度

基于這樣的機制,我們可以給消息設置一個ttl,然后故意不消費消息,等消息過期就會進入死信隊列,我們再消費死信隊列即可。通過這樣的方式,就可以達到同RocketMQ 延遲消息一樣的效果。

在 rabbitmq 中創建死信隊列的操作流程大概是:

  • 創建一個交換機作為死信交換機
  • 在業務隊列中配置 x-dead-letter-exchange 和 x-dead-letter-routing-key,將第一步的交換機設為業務隊列的死信交換機
  • 在死信交換機上創建隊列,并監聽此隊列

死信隊列的設計目的是為了存儲沒有被正常消費的消息,便于排查和重新投遞。死信隊列同樣也沒有對投遞時間做出保證,在第一條消息成為死信之前,后面的消息即使過期也不會投遞為死信。

為了解決這個問題,rabbit 官方推出了延遲投遞插件 rabbitmq-delayed-message-exchange ,推薦使用官方插件來做延時消息。

優點:同RocketMQ 一樣,RabbitMQ 同樣可以使業務解耦,基于其集群的擴展性,也可以實現高可用、高性能的目標。

缺點:死信隊列本質還是一個隊列,隊列都是先進先出,如果隊頭的消息過期時間比較長,就會導致后面過期的消息無法得到及時消費,造成消息阻塞。

總結:除了增加系統復雜度之外,死信隊列的阻塞問題也是需要我們重點關注的。

這里說點題外話,使用 redis 過期監聽或者 rabbitmq 死信隊列做延時任務都是以設計者預想之外的方式使用中間件,這種出其不意必自斃的行為通常會存在某些隱患,比如缺乏一致性和可靠性保證,吞吐量較低、資源泄漏等。比較出名的一個事例是很多人使用 redis 的 list 作為消息隊列,以致于最后作者看不下去寫了 disque 并最后演變為 redis stream。工作中還是盡量不要濫用中間件,用專業的組件做專業的事

最佳實踐

實際上,在數據庫索引設計良好的情況下,定時掃描數據庫中未完成的訂單產生的開銷并沒有想象中那么大。在使用 redisson delayqueue 等定時任務中間件時可以同時使用掃描數據庫的方法作為補償機制,避免中間件故障造成任務丟失。

為什么不建議用MQ實現訂單到期關閉

  1. 時間精度和可靠性問題
  • 延遲隊列的不可預測性:MQ通常為異步通信設計,會受到網絡延遲、隊列長度等多種因素影響,可能無法在確切的時間執行消息消費,導致訂單關閉時間不準確。這對于需要嚴格時間控制的訂單業務來說是一個重要問題。
  • 消息可靠性:盡管MQ能提供相對可靠的消息投遞,但在極端情況下,消息丟失或重復消費依然可能發生。這會給訂單的準確關閉帶來風險,如訂單未關閉或多次錯誤關閉。
  1. 系統復雜性增加
  • 為了實現這一功能,系統需要引入并維護消息隊列,例如ActiveMQ、RabbitMQ或Kafka,這增加了系統的復雜性和運維負擔。
  • 性能與負載問題:需要處理大量與訂單相關的延遲消息,可能會導致MQ系統負載增加,尤其是在高并發環境下,這會影響系統整體性能。
  1. 靈活性和擴展性問題
  • 動態性不足:如果需要調整訂單關閉時間,已經在隊列中的消息很難修改。這會限制系統靈活適應業務需求變化的能力。
  • 復雜的業務邏輯:實現簡單的定時關閉功能需要復雜的處理邏輯,包括消息的發送、接收、消費、異常處理等,這會增加業務流程的復雜性。

并發口訣:一鎖二判三更新

不管我們使用定時任務還是延遲消息時,不可避免的會遇到并發執行任務的情況 (比如重復消費、調度重試等)。

當我們執行任務時,我們可以按照一鎖二判三更新這個口訣來處理。

  1. 鎖定當前需要處理的訂單。
  2. 判斷訂單是否已經更新過對應狀態了
  3. 如果訂單之前沒有更新過狀態了,可以更新并完成相關業務邏輯,否則本次不能更新,也不能完成業務邏輯。
  4. 釋放當前訂單的鎖。

兜底意識 + 配置監控

雖然我們提到了很多的實現策略,現實實戰時依然容易出現問題,比如不合理的操作導致消息丟失。

因此,我們應該具備兜底意識

假如少量消息丟失,我們可以通過每天凌晨跑一次任務,批量將這些未處理的訂單批量取消。這種兜底行為工程實現簡單,同時對系統影響很小。

還有一點,就是配置監控

筆者曾經自研過任務調度系統,應用 A 接入后,從控制臺發現每隔 2 個小時調度應用 A 的任務時,經常發生超時,通過分析,發現應用 A 線程出現了死鎖。

這種問題出現的幾率非常高,因此配置監控特別要必要。

對業務系統來講,監控分為兩個層面:系統監控業務監控

  • 系統監控

在條件允許的情況下,建議關注性能監控,方法可用性監控,方法調用次數監控這三大類。

性能監控

上圖是性能監控的示例圖,性能監控不同時間段性能分布,實時統計 TP99、TP999 、AVG 、MAX 等維度指標,這也是性能調優的重點關注對象。

  • 業務監控

業務監控功能是從業務角度出發,各個應用系統需要從業務層面進行哪些監控,以及提供怎樣的業務層面的監控功能支持業務相關的應用系統。

具體就是對業務數據,業務功能進行監控,實時收集業務流程的數據,并根據設置的策略對業務流程中不符合預期的部分進行預警和報警,并對收集到業務監控數據進行集中統一的存儲和各種方式進行展示。

比如訂單系統中有一個定時結算的服務,每兩分鐘執行一次。我們可以在定時任務 JOB 中添加埋點,并配置業務監控,假如十分鐘該定時任務沒有執行,則發送郵件,短信給相關負責人。

擴展

一筆訂單,在取消的那一刻用戶剛好付款了,怎么辦?

這種情況在正常的業務場景中是有可能出現的,因為訂單都會有定時取消的邏輯,比如10 分鐘或者 15分鐘,而用戶剛好卡在這個時間點進行付款,此時就會出現兩種情況:

  1. 用戶支付成功,支付回調的那一刻支付單剛好還沒取消,而等回調結束,取消支付單的事務提交,支付單取消。此時用戶扣款了,但是對應的權益或資產沒了。

  1. 用戶支付成功,支付回調的那一刻支付單已經被取消。但此時用戶已經扣款,東西卻沒了

可以看到,不論是哪種情況,其實都需要做一定的處理,不然用戶肯定會來投訴!

這種場景無非就是支付單支付成功和取消兩種狀態的“爭奪”,正常情況下,訂單或者支付單都會有狀態機的存在,在當前場景簡單來說有以下兩條路徑:

  1. 待支付->支付中->支付成功
  2. 待支付->支付中->已取消

針對情況1,如果是支付回調取勝,此時的狀態應該已從 支付中->支付成功

針對情況2,如果是取消支付單取勝,此時的狀態應該已從 支付中->已取消

所以我們在修改支付單狀態的時候,基于原始狀態的判斷,就可以做正常的處理,來看下 SOL應該就很清晰了:

# 支付成功
update pay_info set status = 'paySuccess' where orderNo = '1' and status = 'paying';
# 取消
update pay_info set status = 'cancel' where orderNo = '1' and status = 'paying';

重點就是我們加了 status='paying’這個條件,這就能保證情況只有一個能成功,另一個一定失敗。這種其實就是樂觀鎖的方式

  1. 假設情況1成功了,此時用戶已經成功付款,那么狀態已經變為paySucces,取消的SQL必定執行失敗,此時就讓它失敗,不需要做任何別的處理。

  1. 假設情況2成功了,此時訂單已被取消,status已經變為 cancel,支付成功的SOL必定執行失敗,這種情況下我們就需要做逆向處理,即給用戶退款。訂單被取消,用戶的錢也被原路退回,這種處理也沒任何問題

業務優化

針對訂單超時業務,這里在業務上可以做一個小優化,你想想,用戶付款前可能有點掙扎,然后在最后一刻終于下定決心進行付款,這時候卻告知被退款了,用戶很可能就不會再下單了。因此我們在頁面上可以限時訂單取消設置計時為 10分鐘,但實際后端是延遲 11 分鐘取消訂單,這樣就能避免這種情況的發生啦。

Redis 分布式鎖實現

最后除了利用數據庫處理,還可以使用分布式鎖,對一筆訂單加鎖也能保證這筆訂單正常的業務流轉。每次進行取消訂單或付款操作時,首先嘗試獲取訂單的分布式鎖,確保只有一個操作能修改訂單狀態。在分布式系統中,訂單在取消的同時用戶付款的競態問題可以通過分布式鎖來解決。以下是一個具體的、落地的方案,確保訂單狀態的可靠性,避免因并發導致狀態沖突

訂單取消流程:

  1. 超時觸發取消訂單
  2. 取消訂單方法中先獲取該訂單的分布式鎖。如果鎖被其他操作持有(如付款),等待或拋出異常
  3. 若成功獲取鎖,檢查訂單狀態是否已付款:
    • 若訂單未付款,將訂單狀態更新為“已取消”
    • 若訂單已付款,直接跳過這筆訂單的處理。。
    • 釋放分布式鎖,完成取消流程。

訂單付款流程:

  1. 三方支付成功回調。
  2. 后端系統接收回調后,先獲取該訂單的分布式鎖,如果鎖被其他提作持有(如取消),等待或拋出異常(沒有給三方響應成功,三方會重新發起回調)
  3. 若成功獲取鎖,檢查訂單狀態是否為“待支付”:
  4. 若訂單狀態為“待支付”,繼續執行扣款,并將訂單狀態更新為“已付款”。
    • 若訂單狀態為“已取消”,則發起退款,并提示用戶訂單已取消,無法支付。
    • 釋放分布式鎖,完成流程。

?轉自https://www.cnblogs.com/seven97-top/p/18810985


該文章在 2025/4/8 9:10:25 編輯過
關鍵字查詢
相關文章
正在查詢...
點晴ERP是一款針對中小制造業的專業生產管理軟件系統,系統成熟度和易用性得到了國內大量中小企業的青睞。
點晴PMS碼頭管理系統主要針對港口碼頭集裝箱與散貨日常運作、調度、堆場、車隊、財務費用、相關報表等業務管理,結合碼頭的業務特點,圍繞調度、堆場作業而開發的。集技術的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業的高效ERP管理信息系統。
點晴WMS倉儲管理系統提供了貨物產品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質期管理,貨位管理,庫位管理,生產管理,WMS管理系統,標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務都免費,不限功能、不限時間、不限用戶的免費OA協同辦公管理系統。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 欧美精品18videose | 不卡一卡 | 免费观看最新电影和热门影视剧 | 亚洲人成影视在线观看 | 精品国产福利在线观看网站 | 日韩视频一区二区在线观看 | 国产二区在线播放 | 色哟哟精| 天堂a在线观看视频 | 亚洲免费公开视频在线观看 | 秋霞国产午夜伦午夜福利片 | 国产精品日产三级在线观看 | 国产午夜场免费视频在线播放 | 欧美日韩成人一区二区三区 | 日本一区二区成人教育 | 爱我免费视频观看在线www | 色哟哟www视频在线观看高清 | 性欧美一区二区三区在线观看 | 99久视频 | 亚洲中文欧美日韩在线不卡 | 中文字幕在线观看亚洲 | 亚洲精品中文字 | 大地影院高清mv在线观看 | 午夜福利在 | 日本不卡一区二区三区在线 | 亚洲激情乱伦 | 中文日产幕无线码系列 | 亚洲精品乱拍国产一区二区三区 | 国产一区自拍视频 | 亚洲日韩一区二区三区四区高清 | 午夜激情影院 | 中文国产欧美在线观看 | 国产一区二区三区美女 | 欧美黑吊粗大猛烈18p | 军训完被教官灌满精子男男 | 亚洲欧美国产日韩精品 | 亚洲成v人片在线观看福利 一二三四视频 | 天美麻花星空视 | 亚洲h成年动漫在线观看不卡 | 欧美日韩视频在线播放 | 日本顶级rapper潮水老狼 |