Hello,大家好,我是樓下小黑哥~
好久沒寫支付相關的文章了,今天繼續從事老本行~
上次在文章錢被扣走了,但是訂單卻未成功!支付掉單異常最全解決方案提到,支付過程會出現掉單、卡單的情況,這種情況對於用戶來講,體驗非常差,明明自己付了錢,扣了款,但是訂單卻未成功。
上篇文章我們簡單說了下解決方案,這次小黑哥就結合生產實際碰到的情況,給出兩種詳細設計的方案:
- 定時輪詢補償方案
- 延遲消息補償方案
大家可以根據自己系統的實際情況,選擇性參考。
當然了,以下設計方案可能並不完美,如果各位讀者還有其他解決方案,歡迎留言指出,一起討論,一起成長~
歡迎關注我的公眾號:小黑十一點半,獲得日常干貨推送。如果您對我的專題內容感興趣,也可以關注我的博客:studyidea.cn
定時輪詢補償方案
整體流程
這個方案主要采用定時任務,批量查詢掉單記錄,從而驅動查詢具體支付支付結果,然后更新內部訂單。
整體方案流程圖如下:
前三步流程沒什么好說的,正常的支付流程,咱們針對后面幾步具體詳細說下。
第三步調用支付通道之后,如果支付通道端返回支付受理成功或者支付處理中,我們就需要調用第四步,將這類訂單插入掉單表。
如果支付直接成功了,那就正常流程返回即可。
復習一下,網關類支付,比如支付寶、微信支付、網銀支付,這種支付模式,支付通道僅僅返回支付受理成功,具體支付結果需要接收支付通道端的支付通知,這類支付我們將其稱為異步支付。
相應的還有同步支付,比如銀行卡支付,微信、支付寶代扣類支付,這類支付,同步就能返回支付結果。
第五步,補單應用將會定時查詢數據庫,批量查詢掉單記錄。
第六步,補單應用使用線程池,多線程異步的方式發起掉單查詢。
第七步,調用支付通道支付查詢接口。
重點來了,如果第七步支付結果查詢為以下狀態:
- 支付結果為扣款成功
- 支付結果為明確失敗
- 掉單記錄查詢達到最大次數
第八步就會刪除掉單記錄。
最后,如果掉單查詢依舊還是處理中,那么經過一定的延時之后,重復第五步,再次重新掉單補償,直到成功或者查詢到達最大次數。
相關問題
為什么需要新建一張掉單表?不能直接使用支付訂單表,查詢未成功的訂單嗎?
這個問題,實際上確實可以直接使用的支付訂單表,然后批量查詢當天未成功的訂單,補單程序發起支付查詢。
那為什么需要新建一張掉單表?
主要是因為數據庫查詢效率問題,因為支付訂單表每天都會大量記錄新增,隨着時間,這張表記錄將會越來越多,越來越大。
支付記錄越多,批量范圍查詢效率就會變低,查詢速度將會變慢。
所以為了查詢效率,新建一張掉單表。
這張表里僅記錄支付未成功的訂單,所以數據量就會很小,那么查詢效率就會很高。
另外,掉單表里的記錄,不會被永久保存,只是臨時性。當支付結果查詢成功,或者支付結果明確失敗,再或者查詢次數到達規定最大次數,就會刪除掉單記錄。
這就是第八步為什么需要刪除掉單表的原因。
如果需要保存每次掉單表查詢詳情,那么這里建議再新增一張掉單查詢記錄表,保存每一次的查詢記錄。
針對這個方案,如果還有其他問題,歡迎留言。
方案優缺點
定時輪詢補償方案,最大的優點可能就是系統架構方案比較簡單,比較容易實施。
那么這個方案的缺點主要在於定時任務上。
定時任務輪詢方案天然會存在以下不足:
-
輪詢效率稍低
-
每次查詢數據庫,已經被執行過記錄,仍然會被掃描(補單程序將會根據一定策略決定是否發起支付通道查詢),有重復計算的嫌疑
-
時效性不夠好,如果每小時輪詢一次,最差的情況下,時間誤差會達到1小時
-
如果為了解決時效性問題,增加定時任務查詢效率,那么 1 中查詢效率跟 2 的重復計算問題將會更加明顯。
延遲消息補償方案
下面介紹另外一種掉單補償方案,延遲消息補償方案,這個方案整體流程與定時任務方案類似,最大區別可能在於,從一種拉模式變成一種推模式。
整體方案流程圖如下:
這個方案主要流程跟定時方案類似,主要區別在於第四步,第五步,第八步。
第四步的流程從插入掉單表變更為往延遲隊列發送掉單消息。
第五步,補單程序接收掉單消息,然后觸發支付掉單查詢。
第八步,如果第七步支付結果查詢為以下狀態:
- 支付結果為扣款成功
- 支付結果為明確失敗
- 掉單記錄查詢達到最大次數
補單程序將會告知延遲隊列消費成功,延遲隊列將會刪除這條掉單消息。
其他狀態將會告知消費失效,延遲隊列將會在一定延時之后,再次發送掉單消息,然后繼續重復第五步。
延遲隊列
這里的延遲隊列需要自己實現,復雜度還是比較高的,這里給大家推薦幾種實現方案:
第一種,基於 Redis SortedSet 實現延遲隊列。可以參考一下有贊的實現方案https://tech.youzan.com/queuing_delay/
第二種,基於時間輪算法(TimingWheel)實現延遲隊列,具體可以參考 Kafka 延時隊列。
第三種,基於 RocketMQ 延遲消息。
前兩種方案說起來還需要再開發,所以還是比較復雜的。
這里重點說下第三種方案,該方案是 RocketMQ 已經支持的特性,開箱即用,使用起來還是比較簡單的。
RocketMQ 延遲消息支持 18 個等級,分別如下:
1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
消息發送方可以通過以下方式指定延遲等級,對應上方的延遲時間。
Message#setDelayTimeLevel
消息消費方,如果消費失敗,默認將會在消息發送方的的延遲等級基礎上加 1。如果消息消費方需要指定其他的延遲等級,可以使用如下方式:
ConsumeConcurrentlyContext#setDelayLevelWhenNextConsume
RocketMQ 延遲消息,支持的特性還是比較基礎、簡單,不支持自定義延遲時間。不過對於掉單補償的這個場景剛好夠用,但是如果需要自定義延遲的,那還是得采用其他的方案。
方案優缺點
延遲消息的方案相對於定時輪詢方案來講:
- 無需再查詢全部訂單,效率高
- 時效性較好
不過延遲消息這種方案,需要基於延遲隊列,實現起來比較復雜,目前開源實現也比較少。
小結
支付掉單、卡單是支付過程中經常會碰到的事,我們可以采用異步補償的方案,解決該問題。
異步補償方案可以采用如下兩種:
- 定時輪詢補償方案
- 延遲消息補償方案
定時輪詢補償方案實現起來比較簡單,但是時效性稍差。
而延遲消息補償方案總體來說比較優秀,但是實現起來比較復雜。如果沒有自定義的延遲時間的需求,可以直接采用 RocketMQ 延遲消息,簡單快捷。
另外延遲隊列使用場景還是比較多,不僅僅能用在掉單補償上,還可以用於支付關單等場景。所以有能力開發的團隊,可以開發一個通用的延遲隊列、
好了,今天的文章就到這里了。
我是樓下小黑哥,下篇文章再見,886~
歡迎關注我的公眾號:小黑十一點半,獲得日常干貨推送。如果您對我的專題內容感興趣,也可以關注我的博客:studyidea.cn
歷史支付文章推薦
- 錢被扣走了,但是訂單卻未成功!支付掉單異常最全解決方案
- 一筆訂單,但是誤付了兩筆錢!這種重復付款異常到底該如何解決?
- 收款神器!解讀聚合收款碼背后的原理|原創
- 手機沒網了,卻還能支付,這是什么原理?|原創
- 輕輕一掃,立刻扣款,付款碼背后的原理你不想知道嗎?|原創
- 支付渠道路由系統進化史
- 從零開始設計對賬系統
- 微信支付寶接入大全
- 多支付通道路由網關設計
- 銀行卡支付,背后到底發生了什么?
歡迎關注我的公眾號:小黑十一點半,獲得日常干貨推送。如果您對我的專題內容感興趣,也可以關注我的博客:studyidea.cn