我的支付總結(三) 常見問題

枕邊書發表於2017-04-04

支付系統的要求:安全、高效。安全是基本,高效是追求。

要達成兩個目標,難免會遇到各種坑,下面挑幾個典型的問題來講述,並附上簡單的應對方案。

請求超時問題

網路的可靠性要依賴硬體,所以只要是網路呼叫,必然要考慮超時問題,另外因為支付系統一般內部驗證操作多,請求處理時間長,比一般系統超時的概率更大。

支付系統內的每一個請求都應該謹慎處理,而對於無法確定結果的超時請求更不能輕易確定終態,絕對不能像一個簡單的網頁請求一樣重試一次。

一般採取保守策略,將交易狀態保持在一個無害的預設狀態(處理中或未支付),等待下次觸發處理。

請求超時本身易處理,但它導致的後續問題會很多,下面會提到。

終態判斷處理問題

返回碼對映

終態的判斷應該是支付系統內最重要也是最容易踩坑的地方,這個處理的複雜程度真的太依賴三方系統的狀態碼設定了。由於成功和處理中的狀態只有一種,而錯誤則會有各種各樣的原因,有的錯誤可以重試,有的錯誤是系統錯誤。分清交易失敗的原因,關係到系統如何下一步處理交易,所以錯誤明細碼的設計十分重要。

對於一個返回碼設計良好的系統,如微信、支付寶,有業務結果碼和明細錯誤碼之分,我們進行終態判斷和返回碼對映時,可以首先以業務結果碼為準,在業務結果為失敗時,再去檢查明細錯誤碼。

而一個設計不那麼好的系統,將業務結果碼和明細錯誤碼混淆在一起,判斷結果就比較坑,要麼將錯誤碼列出對比,要麼用很危險的else

此問題無法真正避免,只能給出謹慎對映,多向三方系統求證的建議。

查詢無交易記錄

無交易記錄應該是最危險的返回碼了,偏偏因為偶發的網路波動,交易請求受理超時,這個碼還不可避免。 正常邏輯的情況下,無交易記錄是沒發到三方系統,當然是失敗,可是如果在代付交易中,三方系統告訴你,別輕易置失敗,萬一你引數傳錯了呢,錢付出去我們可不賠喲~你還那麼有自信麼。。。

解決方案中最保守的方式當然是作為處理中來處理,然後人工介入處理,這個只能用在交易量不是太大,網路偏穩定的情況下,目前我們只在代付交易中使用此策略。

另外一種方式是搭配請求時的響應資訊來判斷,如果三方系統響應資訊為成功時,查詢為無此交易,那自然是引數或系統邏輯等問題,迅速報警通知處理。如果請求受理時為超時,那麼便可以認為是網路問題沒有傳送成功了,有時候還是要對自己的程式碼有一些信心的。

交易及時性問題

交易及時性不是一個很嚴重的問題,甚至在支付系統中,太有及時性的交易還會使使用者不太放心。但作為一個程式設計師,追求效率是天性嘛,我們還是希望儘早獲取到交易結果,但這也可能導致踩坑。

太早的查詢

查詢太早導致問題會出現在兩種場景:請求超時、三方系統設計問題。

  • 請求超時:請求超時時,系統在過了超時時間後斷開連線不再阻塞,立刻發起查詢請求的話,三方系統可能剛接收到請求,正在進行引數驗證,資料還未落地,此時會收到無此交易的響應,我們將交易作為失敗處理後,交易可能在之後成功。所以查詢一定要有延遲,一定要給三方系統足夠的時間來處理交易。
  • 三方系統設計問題:如xx,在受理交易時使用了中介軟體,中介軟體掛掉後,我們查詢無此交易,但他們重啟中介軟體後又處理交易,竟然又成功了。這個最好在之前能問清三方系統的處理方式,並針對性地設定查詢延遲。

頻繁的查詢

太多頻繁的查詢是無意義的,交易正在三方系統中處理,查詢不會使交易被迅速處理,還會造成網路資源和系統資源的浪費,如果你還記得與三方系統的每一次互動都要重視,那麼查詢日誌也沒法看了。

解決此問題,要:

  • 避免“過早”的查詢,這要考慮三方系統的處理速度;
  • 合理設定查詢時間間隔,一些交易需要更長的處理時間,可以設定梯度時間間隔;
  • 處理無意義的查詢,如查詢“無此交易”,那麼進行多少次結果都不會變,再進行查詢就是無意義的;

隔日賬問題

隔日賬問題在對賬過程中不可避免,由於伺服器時間有差異,交易處理也需要時間,在凌晨附近發生的交易可能會遭遇此問題,這會給對賬造成一定的困擾,但合理的處理方式不會有太大的問題:

自己系統與三方系統對賬檔案不一致時呼叫查詢介面在缺失交易的系統內查詢,先確認交易的存在,再分析交易時間。如在隔日附近,則暫不處理,待次日對賬檔案的對比。

如某一系統內交易不存在,或交易不太可能會發生隔日賬問題,這便需要系統之間人工來處理了,不過這不也是對賬的意義所在麼。

併發效率問題

併發鎖

併發問題在所有系統內都會存在,只是支付系統內處理不好後果會很嚴重,處理方式一般是事務、互斥鎖。

支付系統單系統內使用這些方式也沒有問題,只是鎖的粒度會略大,至少需要保證一個模組內交易處理的原子性。 分散式系統內就要考慮分散式鎖了,這些業內也都有很多解決方案了。

只是加鎖就意味著效率損耗,合理拆分出交易核心模組,並對這些模組新增鎖。另外使用合理的“程式-資料”分配方式,也會減少鎖衝突。

冪等

保持交易中的冪等很重要,它是避免重複支付的基石。 即使系統設計完全,我們還是要追求業務邏輯上的冪等,這也就意味著更多的查詢確認,同時意味著效率下降。

效率下降不可避免,我們可以使用快取來降低效率下降的幅度,在快取中設定交易狀態標識,對交易狀態標識異常的交易再去資料庫查詢。

非同步拆分尷尬

非同步可以抗併發,提高效率,放之四海皆準。支付系統對非同步的依賴更強是因為支付系統由於其處理流程冗長更易達到效率瓶頸。

面對非同步我們首先要解決的問題是非同步拆分的粒度問題,粗粒度的拆分效率能提升的效率有限,細粒度的拆分調控起來不易,處理非同步拆分的粒度,看交易量吧,不做過度設計。

進行非同步拆分時,每一步都需要一個觸發程式,此程式可以是常程式輪詢,也可以是cron程式,事件機制來觸發自然是更好的,但它對訊息佇列的要求很高,設計也較複雜。

除此之外還需要一個確認程式,確保下一步的程式順利接收處理了,在後續程式受理失敗時,能夠及時重試處理。

測試坑

測試是開發中必不可少的步驟,自己測和測試來測,總要完全走一遍流程才敢放心上線。

支付測試略坑了: 首先測試環境的佈置,支付系統牽涉到多個三方系統的互動,靠譜的系統都會提供測試系統,可是難免有些系統不提供測試環境,或者測試限制頗多,限支付行,限金額等,還要提防其測試系統忽然就掛了。然後是線上測試,更要小心翼翼,一個不慎就是資金損失。

為了提供良好的測試環境,我們引入“MOCK”功能,mock 中文意思為“模仿”,即通過模擬三方系統的返回值來測試本系統的穩定性。

但 mock 的程式碼侵入性略強,完整的 mock 模組必然有if else語句的存在,由於支付相關的系統較多,要搭建完整的 mock 系統不容易,單點 mock 需要各處埋點。整體 mock 的又不便於測試特定功能。

小結

支付的坑包括但不限於本文介紹的這些,可能還會有其他奇怪的問題,文章沒有介紹到。

若想盡量避免支付系統的坑,那麼一定要保持著保守的態度,將狀態或交易保持無害。有些需要事務操作,但無法使用典型事務的場景,將次要的一開始執行,即使出了問題,有重試、回滾等操作,也不會造成影響。

支付總結暫時到此為止。

相關文章