接口調(diào)用存在的問題
現(xiàn)如今我們的系統(tǒng)大多拆分為分布式SOA运提,或者微服務(wù),一套系統(tǒng)中包含了多個(gè)子系統(tǒng)服務(wù)癣丧,而一個(gè)子系統(tǒng)服務(wù)往往會(huì)去調(diào)用另一個(gè)服務(wù)栈妆,而服務(wù)調(diào)用服務(wù)無非就是使用RPC通信或者restful,既然是通信掏呼,那么就有可能在服務(wù)器處理完畢后返回結(jié)果的時(shí)候掛掉铅檩,這個(gè)時(shí)候用戶端發(fā)現(xiàn)很久沒有反應(yīng),那么就會(huì)多次點(diǎn)擊按鈕拾给,這樣請(qǐng)求有多次兔沃,那么處理數(shù)據(jù)的結(jié)果是否要統(tǒng)一呢?那是肯定的额衙!尤其在支付場(chǎng)景。
什么是接口冪等性
接口冪等性就是用戶對(duì)于同一操作發(fā)起的一次請(qǐng)求或者多次請(qǐng)求的結(jié)果是一致的县踢,不會(huì)因?yàn)槎啻吸c(diǎn)擊而產(chǎn)生了副作用伟件。舉個(gè)最簡(jiǎn)單的例子,那就是支付斧账,用戶購(gòu)買商品后支付咧织,支付扣款成功,但是返回結(jié)果的時(shí)候網(wǎng)絡(luò)異常索抓,此時(shí)錢已經(jīng)扣了毯炮,用戶再次點(diǎn)擊按鈕,此時(shí)會(huì)進(jìn)行第二次扣款篮幢,返回結(jié)果成功为迈,用戶查詢余額返發(fā)現(xiàn)多扣錢了,流水記錄也變成了兩條...,這就沒有保證接口的冪等性
什么情況下需要保證接口的冪等性
在增刪改查4個(gè)操作中搜锰,尤為注意就是增加或者修改耿战,
A: 查詢操作
查詢對(duì)于結(jié)果是不會(huì)有改變的,查詢一次和查詢多次狈涮,在數(shù)據(jù)不變的情況下鸭栖,查詢結(jié)果是一樣的。select是天然的冪等操作
B: 刪除操作
刪除一次和多次刪除都是把數(shù)據(jù)刪除松却。(注意可能返回結(jié)果不一樣,刪除的數(shù)據(jù)不存在肉渴,返回0带射,刪除的數(shù)據(jù)多條循狰,返回結(jié)果多個(gè),在不考慮返回結(jié)果的情況下,刪除操作也是具有冪等性的)
C: 更新操作
修改在大多場(chǎng)景下結(jié)果一樣,但是如果是增量修改是需要保證冪等性的,如下例子:
把表中id為XXX的記錄的A字段值設(shè)置為1,這種操作不管執(zhí)行多少次都是冪等的
把表中id為XXX的記錄的A字段值增加1,這種操作就不是冪等的
D: 新增操作
增加在重復(fù)提交的場(chǎng)景下會(huì)出現(xiàn)冪等性問題,如以上的支付問題
那么如何設(shè)計(jì)接口才能做到冪等呢绪钥?
常見的兩種實(shí)現(xiàn)方案: 1. 通過代碼邏輯判斷實(shí)現(xiàn) 2. 使用token機(jī)制實(shí)現(xiàn) 下面以支付系統(tǒng)為例,分別對(duì)接口的冪等性進(jìn)行說明與實(shí)現(xiàn)
A: 通過代碼邏輯判斷實(shí)現(xiàn)接口冪等性,只能針對(duì)一些滿足判斷的邏輯實(shí)現(xiàn),具有一定局限性
用戶購(gòu)買商品的訂單系統(tǒng)與支付系統(tǒng);訂單系統(tǒng)負(fù)責(zé)記錄用戶的購(gòu)買記錄已經(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)因?yàn)榫W(wǎng)絡(luò)原因狐血,沒有獲取到確切的結(jié)果易核,因此訂單系統(tǒng)需要重試。由上圖可見报亩,支付系統(tǒng)并沒有做到接口的冪等性井氢,訂單系統(tǒng)第一次調(diào)用和第二次調(diào)用花竞,用戶分別被扣了兩次錢掸哑,不符合冪等性原則(同一個(gè)訂單零远,無論是調(diào)用了多少次,用戶都只會(huì)扣款一次)摔癣。如果需要支持冪等性纬向,付款接口需要修改為以下接口:
boolean pay(int orderId,int accountId,BigDecimal amount)
通過orderId來標(biāo)定訂單的唯一性,付款系統(tǒng)只要檢測(cè)到訂單已經(jīng)支付過琢岩,則第二次調(diào)用不會(huì)扣款而會(huì)直接返回結(jié)果:
在不同的業(yè)務(wù)中不同接口需要有不同的冪等性师脂,特別是在分布式系統(tǒng)中糕篇,因?yàn)榫W(wǎng)絡(luò)原因而未能得到確定的結(jié)果,往往需要支持接口冪等性谒府。
隨著分布式系統(tǒng)及微服務(wù)的普及,因?yàn)榫W(wǎng)絡(luò)原因而導(dǎo)致調(diào)用系統(tǒng)未能獲取到確切的結(jié)果從而導(dǎo)致重試盛龄,這就需要被調(diào)用系統(tǒng)具有冪等性。例如上文所闡述的支付系統(tǒng)匿值,針對(duì)同一個(gè)訂單保證支付的冪等性挟憔,一旦訂單的支付狀態(tài)確定之后绊谭,以后的操作都會(huì)返回相同的結(jié)果,對(duì)用戶的扣款也只會(huì)有一次趟大。這種接口的冪等性叽讳,簡(jiǎn)化到數(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是訂單號(hào)民褂。
在上文中提到的訂單系統(tǒng),訂單具有自己的狀態(tài)(orderStatus),訂單狀態(tài)存在一定的流轉(zhuǎn)。訂單首先有提交(0)吃挑,付款中(1)街立,付款成功(2),付款失敼溆獭(3),簡(jiǎn)化之后其流轉(zhuǎn)路徑如圖:
當(dāng)orderStatus = 1 時(shí),其前置狀態(tài)只能是0,也就是說將orderStatus由0->1 是需要冪等性的
update Order set orderStatus = 1 where OrderId = 'orderid' and orderStatus = 0
當(dāng)orderStatus 處于0颊亮,1兩種狀態(tài)時(shí)绍在,對(duì)訂單執(zhí)行0->1 的狀態(tài)流轉(zhuǎn)操作應(yīng)該是具有冪等性的件舵。這時(shí)候需要在執(zhí)行update操作之前檢測(cè)orderStatus是否已經(jīng)=1临梗,如果已經(jīng)=1則直接返回true即可吃沪。
但是如果此時(shí)orderStatus =2,再進(jìn)行訂單狀態(tài)0->1時(shí)操作就無法成功,但是冪等性是針對(duì)同一個(gè)請(qǐng)求的什猖,也就是針對(duì)同一個(gè)requestid保持冪等票彪。這時(shí)候再執(zhí)行
update Order set orderStatus = 1 where OrderId = 'orderid' and orderStatus = 0
接口會(huì)返回失敗红淡,系統(tǒng)沒有產(chǎn)生修改,如果再發(fā)一次降铸,requestid是相同的在旱,對(duì)系統(tǒng)同樣沒有產(chǎn)生修改。
B: 使用token機(jī)制實(shí)現(xiàn)接口冪等性,通用性強(qiáng)的實(shí)現(xiàn)方法
token機(jī)制實(shí)現(xiàn)步驟:1.生成全局唯一的token,token放到redis或jvm內(nèi)存,token會(huì)在頁(yè)面跳轉(zhuǎn)時(shí)獲取.存放到pageScope中,支付請(qǐng)求提交先獲取token2.提交后后臺(tái)校驗(yàn)token推掸,執(zhí)行提交邏輯,提交成功同時(shí)刪除token桶蝎,生成新的token更新redis ,這樣當(dāng)?shù)谝淮翁峤缓髏oken更新了,頁(yè)面再次提交攜帶的token是已刪除的token后臺(tái)驗(yàn)證會(huì)失敗不讓提交 token特點(diǎn):? 要申請(qǐng),一次有效性谅畅,可以限流 注意: redis要用刪除操作來判斷token登渣,刪除成功代表token校驗(yàn)通過,如果用select+delete來校驗(yàn)token毡泻,存在并發(fā)問題胜茧,不建議使用 ? ? ? ? 歡迎工作一到五年的Java工程師朋友們加入Java群:?741514154
群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)牙捉、高性能及分布式竹揍、Jvm性能調(diào)優(yōu)、Spring源碼邪铲,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來學(xué)習(xí)提升自己无拗,不要再用"沒有時(shí)間“來掩飾自己思想上的懶惰带到!趁年輕,使勁拼英染,給未來的自己一個(gè)交代揽惹!