接口的幂等性原则
接口調(diào)用存在的問題
?現(xiàn)如今我們的系統(tǒng)大多拆分為分布式SOA,或者微服務(wù),一套系統(tǒng)中包含了多個子系統(tǒng)服務(wù),而一個子系統(tǒng)服務(wù)往往會去調(diào)用另一個服務(wù),而服務(wù)調(diào)用服務(wù)無非就是使用RPC通信或者restful,既然是通信,那么就有可能在服務(wù)器處理完畢后返回結(jié)果的時候掛掉,這個時候用戶端發(fā)現(xiàn)很久沒有反應(yīng),那么就會多次點擊按鈕,這樣請求有多次,那么處理數(shù)據(jù)的結(jié)果是否要統(tǒng)一呢?那是肯定的!尤其在支付場景。
什么是接口冪等性
接口冪等性就是用戶對于同一操作發(fā)起的一次請求或者多次請求的結(jié)果是一致的,不會因為多次點擊而產(chǎn)生了副作用。舉個最簡單的例子,那就是支付,用戶購買商品后支付,支付扣款成功,但是返回結(jié)果的時候網(wǎng)絡(luò)異常,此時錢已經(jīng)扣了,用戶再次點擊按鈕,此時會進行第二次扣款,返回結(jié)果成功,用戶查詢余額返發(fā)現(xiàn)多扣錢了,流水記錄也變成了兩條...,這就沒有保證接口的冪等性
什么情況下需要保證接口的冪等性
在增刪改查4個操作中,尤為注意就是增加或者修改,
A: 查詢操作
查詢對于結(jié)果是不會有改變的,查詢一次和查詢多次,在數(shù)據(jù)不變的情況下,查詢結(jié)果是一樣的。select是天然的冪等操作
B: 刪除操作
刪除一次和多次刪除都是把數(shù)據(jù)刪除。(注意可能返回結(jié)果不一樣,刪除的數(shù)據(jù)不存在,返回0,刪除的數(shù)據(jù)多條,返回結(jié)果多個,在不考慮返回結(jié)果的情況下,刪除操作也是具有冪等性的)
C: 更新操作
修改在大多場景下結(jié)果一樣,但是如果是增量修改是需要保證冪等性的,如下例子:
把表中id為XXX的記錄的A字段值設(shè)置為1,這種操作不管執(zhí)行多少次都是冪等的
把表中id為XXX的記錄的A字段值增加1,這種操作就不是冪等的
D: 新增操作
增加在重復(fù)提交的場景下會出現(xiàn)冪等性問題,如以上的支付問題
-
那么如何設(shè)計接口才能做到冪等呢?
常見的兩種實現(xiàn)方案: 1. 通過代碼邏輯判斷實現(xiàn) 2. 使用token機制實現(xiàn) 下面以支付系統(tǒng)為例,分別對接口的冪等性進行說明與實現(xiàn)
A: 通過代碼邏輯判斷實現(xiàn)接口冪等性,只能針對一些滿足判斷的邏輯實現(xiàn),具有一定局限性
用戶購買商品的訂單系統(tǒng)與支付系統(tǒng);訂單系統(tǒng)負責(zé)記錄用戶的購買記錄已經(jīng)訂單的流轉(zhuǎn)狀態(tài)(orderStatus),支付系統(tǒng)用于付款,提供如下接口,訂單系統(tǒng)與支付系統(tǒng)通過分布式網(wǎng)絡(luò)交互。
boolean pay(int accountid,BigDecimal amount) //用于付款,扣除用戶的
這種情況下,支付系統(tǒng)已經(jīng)扣款,但是訂單系統(tǒng)因為網(wǎng)絡(luò)原因,沒有獲取到確切的結(jié)果,因此訂單系統(tǒng)需要重試。由上圖可見,支付系統(tǒng)并沒有做到接口的冪等性,訂單系統(tǒng)第一次調(diào)用和第二次調(diào)用,用戶分別被扣了兩次錢,不符合冪等性原則(同一個訂單,無論是調(diào)用了多少次,用戶都只會扣款一次)。如果需要支持冪等性,付款接口需要修改為以下接口:
boolean pay(int orderId,int accountId,BigDecimal amount)
通過orderId來標(biāo)定訂單的唯一性,付款系統(tǒng)只要檢測到訂單已經(jīng)支付過,則第二次調(diào)用不會扣款而會直接返回結(jié)果:
在不同的業(yè)務(wù)中不同接口需要有不同的冪等性,特別是在分布式系統(tǒng)中,因為網(wǎng)絡(luò)原因而未能得到確定的結(jié)果,往往需要支持接口冪等性。
隨著分布式系統(tǒng)及微服務(wù)的普及,因為網(wǎng)絡(luò)原因而導(dǎo)致調(diào)用系統(tǒng)未能獲取到確切的結(jié)果從而導(dǎo)致重試,這就需要被調(diào)用系統(tǒng)具有冪等性。例如上文所闡述的支付系統(tǒng),針對同一個訂單保證支付的冪等性,一旦訂單的支付狀態(tài)確定之后,以后的操作都會返回相同的結(jié)果,對用戶的扣款也只會有一次。這種接口的冪等性,簡化到數(shù)據(jù)層面的操作:
update userAmount set amount = amount - 'value' ,paystatus = 'paid' where orderId= 'orderid' and paystatus = 'unpay'
其中value是用戶要減少的訂單,paystatus代表支付狀態(tài),paid代表已經(jīng)支付,unpay代表未支付,orderid是訂單號。
在上文中提到的訂單系統(tǒng),訂單具有自己的狀態(tài)(orderStatus),訂單狀態(tài)存在一定的流轉(zhuǎn)。訂單首先有提交(0),付款中(1),付款成功(2),付款失敗(3),簡化之后其流轉(zhuǎn)路徑如圖:
當(dāng)orderStatus = 1 時,其前置狀態(tài)只能是0,也就是說將orderStatus由0->1 是需要冪等性的
update Order set orderStatus = 1 where OrderId = 'orderid' and orderStatus = 0
當(dāng)orderStatus 處于0,1兩種狀態(tài)時,對訂單執(zhí)行0->1 的狀態(tài)流轉(zhuǎn)操作應(yīng)該是具有冪等性的。這時候需要在執(zhí)行update操作之前檢測orderStatus是否已經(jīng)=1,如果已經(jīng)=1則直接返回true即可。
但是如果此時orderStatus = 2,再進行訂單狀態(tài)0->1 時操作就無法成功,但是冪等性是針對同一個請求的,也就是針對同一個requestid保持冪等。
這時候再執(zhí)行
update Order set orderStatus = 1 where OrderId = 'orderid' and orderStatus = 0
接口會返回失敗,系統(tǒng)沒有產(chǎn)生修改,如果再發(fā)一次,requestid是相同的,對系統(tǒng)同樣沒有產(chǎn)生修改。
B: 使用token機制實現(xiàn)接口冪等性,通用性強的實現(xiàn)方法
?token機制實現(xiàn)步驟:
?1. 生成全局唯一的token,token放到redis或jvm內(nèi)存,token會在頁面跳轉(zhuǎn)時獲取.存放到pageScope中,支付請求提交先獲取token
?2. 提交后后臺校驗token,執(zhí)行提交邏輯,提交成功同時刪除token,生成新的token更新redis ,這樣當(dāng)?shù)谝淮翁峤缓髏oken更新了,頁面再次提交攜帶的token是已刪除的token后臺驗證會失敗不讓提交
?token特點: ? 要申請,一次有效性,可以限流
?注意: redis要用刪除操作來判斷token,刪除成功代表token校驗通過,如果用select+delete來校驗token,存在并發(fā)問題,不建議使用
總結(jié)
- 上一篇: RESTful API 最佳实践
- 下一篇: 深入理解Nginx工作原理