關於分布式系統的數據一致性問題(二)


分布式系統的數據一致性問題(一)里面,簡單的介紹了分布式數據的同步問題,上面的問題比較抽象,在目前的互聯網應用中還很少見,這次在通過一個比較常見的例子,讓大家更深入的了解一下分布式系統設計中關於數據一致性的問題

 

這次我們拿我們經常使用的功能來考慮吧,最近網購比較熱門,就以京東為例的,我們來看看京東的一個簡單的購物流程

 

用戶在京東上下了一個訂單,發現自己在京東的賬戶里面有余額,然后使用余額支付,支付成功之后,訂單狀態修改為支付成功,然后通知倉庫發貨。假設訂單系統,支付系統,倉庫系統是三個獨立的應用,是獨立部署的,系統之間通過遠程服務調用。

訂單的有三個狀態:I:初始 P:已支付 W:已出庫,訂單金額100, 會員帳戶余額200

如果整個流程比較順利,正常情況下,訂單的狀態會變為I->P->W,會員帳戶余額100,訂單出庫。

 

但是如果流程不順利了?考慮以下幾種情況

1:訂單系統調用支付系統支付訂單,支付成功,但是返回給訂單系統數據超時,訂單還是I(初始狀態),但是此時會員帳戶余額100,會員肯定會馬上找京東罵京東,為啥不給老子發貨,我都付錢了

2:訂單系統調用支付系統成功,狀態也已經更新成功,但是通知倉庫發貨失敗,這個時候訂單是P(已支付)狀態,此時會員帳戶余額是100,但是倉庫不會發貨。會員也要罵京東。

3:訂單系統調用支付系統成功,狀態也已經更新成功,然后通知倉庫發貨,倉庫告訴訂單系統,沒有貨了。這個時候數據狀態和第二種情況一樣。

 

對於問題一,我們來分析一下解決方案,能想到的解決方案如下

1 假設調用支付系統支付訂單的時候先不扣錢,訂單狀態更新完成之后,在通知支付系統你扣錢

如果采用這種設計方案,那么在同一時刻,這個用戶,又支付了另外一筆訂單,訂單價格200,順利完成了整個訂單支付流程,由於當前訂單的狀態已經變成了支付成功,但是實際用戶已經沒有錢支付了,這筆訂單的狀態就不一致了。即使用戶在同一個時刻沒有進行另外的訂單支付行為,通知支付系統扣錢這個動作也有可能完不成,因為也有可能失敗,反而增加了系統的復雜性。

 

2 訂單系統自動發起重試,多重試幾次,例如三次,直到扣款成功為止。

這個看起來也是不錯的考慮,但是和解決方案一樣,解決不了問題,還會帶來新的問題,假設訂單系統第一次調用支付系統成功,但是沒有辦法收到應答,訂單系統又發起調用,完了,重復支付,一次訂單支付了200。

假設支付系統正在發布,你重試多少次都一樣,都會失敗。這個時候用戶在等待,你怎么處理?

 

3 在第二種方案的基礎上,我們先解決訂單的重復支付行為,我們需要在支付系統上對訂單號進行控制,一筆訂單如果已經支付成功,不能在進行支付。返回重復支付標識。那么訂單系統根據返回的標識,更新訂單狀態。

接下來解決重試問題,我們假設應用上重試三次,如果三次都失敗,先返回給用戶提示支付結果未知。假設這個時候用戶重新發起支付,訂單系統調用支付系統,發現訂單已經支付,那么繼續下面的流程。如果會員沒有發起支付,系統定時(一分鍾一次)去核對訂單狀態,如果發現已經被支付,則繼續后續的流程。

 

這種方案,用戶體驗非常差,告訴用戶支付結果未知,用戶一定會罵你,你丫咋回事情,我明明支付了,你告訴我未知。假設告訴用戶支付失敗,萬一實際是成功的咋辦。你告訴用戶支付成功,萬一支付失敗咋辦。

 

4 第三種方案能夠解決訂單和支付數據的一致性問題,但是用戶體驗非常差。當然這種情況比較可能是少數,可以犧牲這一部分的用戶體驗,我們還有沒有更好的解決方案,既能照顧用戶體驗,又能夠保證資金的安全性。

我們再回來看看第一種方案,我們先不扣錢,但是有木有辦法讓這一部分錢不讓用戶使用,對了,我們先把這一部分錢凍結起來,訂單系統先調用支付系統成功的時候,支付系統先不扣錢,而是先把錢凍結起來,不讓用戶給其他訂單支付,然后等訂單系統把訂單狀態更新為支付成功的時候,再通知支付系統,你扣錢吧,這個時候支付系統扣錢,完成后續的操作。

 

看起來這個方案不錯,我們仔細在分析一下流程,這個方案還存在什么問題,假設訂單系統在調用支付系統凍結的時候,支付系統凍結成功,但是訂單系統超時,這個時候返回給用戶,告知用戶支付失敗,如果用戶再次支付這筆訂單,那么由於支付系統進行控制,告訴訂單系統凍結成功,訂單系統更新狀態,然后通知支付系統,扣錢吧。如果這個時候通知失敗,木有問題,反正錢都已經是凍結的了,用戶不能用,我只要定時掃描訂單和支付狀態,進行扣錢而已。

 

那么如果變態的用戶重新拍下來一筆訂單,100塊錢,對新的訂單進行支付,這個時候由於先前那一筆訂單的錢被凍結了,這個時候用戶余額剩余100,凍結100,發現可用的余額足夠,那就直接在對用戶扣錢。這個時候余額剩余0,凍結100。先前那一筆怎么辦,一個辦法就是定時掃描,發現訂單狀態是初始的話,就對用戶的支付余額進行解凍處理。這個時候用戶的余額變成100,訂單數據和支付數據又一致了。假設原先用戶余額只有100,被凍結了,用戶重新下單,支付的時候就失敗了啊,的確會發生這一種情況,所以要盡可能的保證在第一次訂單結果不明確的情況,盡早解凍用戶余額,比如10秒之內。但是不管如何快速,總有數據不一致的時刻,這個是沒有辦法避免的。

 

第二種情況和第三種情況如何處理,下次在分析吧。

由於互聯網目前越來越強調分布式架構,如果是交易類系統,面臨的將會是分布式事務上的挑戰。當然目前有很多開源的分布式事務產品,例如java JPA,但是這種解決方案的成本是非常高的,而且實現起來非常復雜,效率也比較低下。對於極端的情況:例如發布,故障的時候都是沒有辦法保證強一致性的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM