支付系統的要求:安全、高效。安全是基本,高效是追求。javascript
要達成兩個目標,不免會遇到各類坑,下面挑幾個典型的問題來說述,並附上簡單的應對方案。php
網絡的可靠性要依賴硬件,因此只要是網絡調用,必然要考慮超時問題,另外由於支付系統通常內部驗證操做多,請求處理時間長,比通常系統超時的機率更大。css
支付系統內的每個請求都應該謹慎處理,而對於沒法肯定結果的超時請求更不能輕易肯定終態,絕對不能像一個簡單的網頁請求同樣重試一次。html
通常採起保守策略,將交易狀態保持在一個無害的默認狀態(處理中或未支付),等待下次觸發處理。java
請求超時自己易處理,但它致使的後續問題會不少,下面會提到。python
終態的判斷應該是支付系統內最重要也是最容易踩坑的地方,這個處理的複雜程度真的太依賴三方系統的狀態碼設置了。因爲成功和處理中的狀態只有一種,而錯誤則會有各類各樣的緣由,有的錯誤能夠重試,有的錯誤是系統錯誤。分清交易失敗的緣由,關係到系統如何下一步處理交易,因此錯誤明細碼的設計十分重要。nginx
對於一個返回碼設計良好的系統,如微信、支付寶,有業務結果碼和明細錯誤碼之分,咱們進行終態判斷和返回碼映射時,能夠首先以業務結果碼爲準,在業務結果爲失敗時,再去檢查明細錯誤碼。git
而一個設計不那麼好的系統,將業務結果碼和明細錯誤碼混淆在一塊兒,判斷結果就比較坑,要麼將錯誤碼列出對比,要麼用很危險的else
。程序員
此問題沒法真正避免,只能給出謹慎映射,多向三方系統求證的建議。github
無交易記錄應該是最危險的返回碼了,恰恰由於偶發的網絡波動,交易請求受理超時,這個碼還不可避免。 正常邏輯的狀況下,無交易記錄是沒發到三方系統,固然是失敗,但是若是在代付交易中,三方系統告訴你,別輕易置失敗,萬一你參數傳錯了呢,錢付出去咱們可不賠喲~你還那麼有自信麼。。。
解決方案中最保守的方式固然是做爲處理中來處理,而後人工介入處理,這個只能用在交易量不是太大,網絡偏穩定的狀況下,目前咱們只在代付交易中使用此策略。
另一種方式是搭配請求時的響應信息來判斷,若是三方系統響應信息爲成功時,查詢爲無此交易,那天然是參數或系統邏輯等問題,迅速報警通知處理。若是請求受理時爲超時,那麼即可以認爲是網絡問題沒有發送成功了,有時候仍是要對本身的代碼有一些信心的。
交易及時性不是一個很嚴重的問題,甚至在支付系統中,太有及時性的交易還會使用戶不太放心。但做爲一個程序員,追求效率是天性嘛,咱們仍是但願儘早獲取到交易結果,但這也可能致使踩坑。
查詢太早致使問題會出如今兩種場景:請求超時、三方系統設計問題。
太多頻繁的查詢是無心義的,交易正在三方系統中處理,查詢不會使交易被迅速處理,還會形成網絡資源和系統資源的浪費,若是你還記得與三方系統的每一次交互都要重視,那麼查詢日誌也無法看了。
解決此問題,要:
隔日帳問題在對帳過程當中不可避免,因爲服務器時間有差別,交易處理也須要時間,在凌晨附近發生的交易可能會遭遇此問題,這會給對帳形成必定的困擾,但合理的處理方式不會有太大的問題:
本身系統與三方系統對帳文件不一致時調用查詢接口在缺失交易的系統內查詢,先確認交易的存在,再分析交易時間。如在隔日附近,則暫不處理,待第二天對帳文件的對比。
如某一系統內交易不存在,或交易不太可能會發生隔日帳問題,這便須要系統之間人工來處理了,不過這不也是對帳的意義所在麼。
併發問題在全部系統內都會存在,只是支付系統內處理很差後果會很嚴重,處理方式通常是事務、互斥鎖。
支付系統單系統內使用這些方式也沒有問題,只是鎖的粒度會略大,至少須要保證一個模塊內交易處理的原子性。 分佈式系統內就要考慮分佈式鎖了,這些業內也都有不少解決方案了。
只是加鎖就意味着效率損耗,合理拆分出交易核心模塊,並對這些模塊添加鎖。另外使用合理的「進程-數據」分配方式,也會減小鎖衝突。
保持交易中的冪等很重要,它是避免重複支付的基石。 即便系統設計徹底,咱們仍是要追求業務邏輯上的冪等,這也就意味着更多的查詢確認,同時意味着效率降低。
效率降低不可避免,咱們可使用緩存來下降效率降低的幅度,在緩存中設置交易狀態標識,對交易狀態標識異常的交易再去數據庫查詢。
異步能夠抗併發,提升效率,放之四海皆準。支付系統對異步的依賴更強是由於支付系統因爲其處理流程冗長更易達到效率瓶頸。
面對異步咱們首先要解決的問題是異步拆分的粒度問題,粗粒度的拆分效率能提高的效率有限,細粒度的拆分調控起來不易,處理異步拆分的粒度,看交易量吧,不作過分設計。
進行異步拆分時,每一步都須要一個觸發進程,此進程能夠是常進程輪詢,也能夠是cron進程,事件機制來觸發天然是更好的,但它對消息隊列的要求很高,設計也較複雜。
除此以外還須要一個確認進程,確保下一步的進程順利接收處理了,在後續進程受理失敗時,可以及時重試處理。
測試是開發中必不可少的步驟,本身測和測試來測,總要徹底走一遍流程纔敢放心上線。
支付測試略坑了: 首先測試環境的佈置,支付系統牽涉到多個三方系統的交互,靠譜的系統都會提供測試系統,但是不免有些系統不提供測試環境,或者測試限制頗多,限支付行,限金額等,還要提防其測試系統突然就掛了。而後是線上測試,更要當心翼翼,一個不慎就是資金損失。
爲了提供良好的測試環境,咱們引入「MOCK」功能,mock 中文意思爲「模仿」,即經過模擬三方系統的返回值來測試本系統的穩定性。
但 mock 的代碼侵入性略強,完整的 mock 模塊必然有if else
語句的存在,因爲支付相關的系統較多,要搭建完整的 mock 系統不容易,單點 mock 須要各處埋點。總體 mock 的又不便於測試特定功能。
支付的坑包括但不限於本文介紹的這些,可能還會有其餘奇怪的問題,文章沒有介紹到。
若想盡可能避免支付系統的坑,那麼必定要保持着保守的態度,將狀態或交易保持無害。有些須要事務操做,但沒法使用典型事務的場景,將次要的一開始執行,即便出了問題,有重試、回滾等操做,也不會形成影響。
支付總結暫時到此爲止。