1. 程式人生 > >Java學習資料支付介面的冪等性設計

Java學習資料支付介面的冪等性設計

1. 什麼是冪等性?

在數學中的冪等性定義:

在某二元運算下,冪等元素是指被自己重複運算(或對於函式是為複合)的結果等於它自己的元素。例如,乘法下唯一兩個冪等實數為0和1。 即 s * s = s

某一元運算為冪等時,其作用在任一元素兩次後會和其作用一次的結果相同。例如,高斯符號便是冪等的,即f(f(x)) = f(x)。

 

在HTTP/1.1規範中的冪等性定義:

如果一個請求方法在伺服器上多次執行的預期影響與它只執行一次相同,那麼這個請求方法就被認為具有冪等性。

HTTP的冪等性指的是一次和多次請求某一個資源應該具有相同的副作用。如通過PUT介面將資料的Status置為1,無論是第一次執行還是多次執行,獲取到的結果應該是相同的,即執行完成之後Status =1。

 

2. 何種介面提供冪等性

2.1 HTTP支援冪等性的介面

在HTTP規範中定義GET,PUT和DELETE方法應該具有冪等性。

GET方法

GET方法是向伺服器查詢,不會對系統產生副作用,具有冪等性(不代表每次請求都是相同的結果)

PUT方法

也就是說PUT方法首先判斷系統中是否有相關的記錄,如果有記錄則更新該記錄,如果沒有則新增記錄。

DELETE 方法

DELETE方法是刪除伺服器上的相關記錄。

 

2.2 實際業務

比如現在有這樣一個系統,使用者購買商品的訂單系統與支付系統;訂單系統負責記錄使用者的購買記錄已經訂單的流轉狀態(orderStatus),支付系統用於付款,提供

boolean pay(int accountid,BigDecimal amount) //用於付款,扣除使用者的

介面,訂單系統與支付系統通過分散式網路互動。

這種情況下,支付系統已經扣款,但是訂單系統因為網路原因,沒有獲取到確切的結果,因此訂單系統需要重試。 由上圖可見,支付系統並沒有做到介面的冪等性,訂單系統第一次呼叫和第二次呼叫,使用者分別被扣了兩次錢,不符合冪等性原則(同一個訂單,無論是呼叫了多少次,使用者都只會扣款一次)。 如果需要支援冪等性,付款介面需要修改為以下介面:

boolean pay(int orderId,int accountId,BigDecimal amount)

通過orderId來標定訂單的唯一性,付款系統只要檢測到訂單已經支付過,則第二次呼叫不會扣款而會直接返回結果:

在不同的業務中不同介面需要有不同的冪等性,特別是在分散式系統中,因為網路原因而未能得到確定的結果,往往需要支援介面冪等性。

 

3. 分散式系統介面冪等性

隨著分散式系統及微服務的普及,因為網路原因而導致呼叫系統未能獲取到確切的結果從而導致重試,這就需要被呼叫系統具有冪等性。 例如上文所闡述的支付系統,針對同一個訂單保證支付的冪等性,一旦訂單的支付狀態確定之後,以後的操作都會返回相同的結果,對使用者的扣款也只會有一次。這種介面的冪等性,簡化到資料層面的操作:

 

update userAmount set amount = amount - 'value' ,paystatus = 'paid' where orderId= 'orderid' and paystatus = 'unpay'

其中value是使用者要減少的訂單,paystatus代表支付狀態,paid代表已經支付,unpay代表未支付,orderid是訂單號。 在上文中提到的訂單系統,訂單具有自己的狀態(orderStatus),訂單狀態存在一定的流轉。

 

訂單首先有提交(0),付款中(1),付款成功(2),付款失敗(3),簡化之後其流轉路徑如圖:

當orderStatus = 1 時,其前置狀態只能是0,也就是說將orderStatus由0->1 是需要冪等性的

 

update Order set orderStatus = 1 where OrderId = 'orderid' and orderStatus = 0

當orderStatus 處於0,1兩種狀態時,對訂單執行0->1 的狀態流轉操作應該是具有冪等性的。 這時候需要在執行update操作之前檢測orderStatus是否已經=1,如果已經=1則直接返回true即可。

 

但是如果此時orderStatus = 2,再進行訂單狀態0->1 時操作就無法成功,但是冪等性是針對同一個請求的,也就是針對同一個requestid保持冪等。

 

這時候再執行

update Order set orderStatus = 1 where OrderId = 'orderid' and orderStatus = 0

介面會返回失敗,系統沒有產生修改,如果再發一次,requestid是相同的,對系統同樣沒有產生修改。