高并发下的幂等策略分析
 
雙十一,零點(diǎn)剛開(kāi)始,小明就迫不及待地點(diǎn)擊提交訂單按鈕,1秒,2秒,3秒,沒(méi)反應(yīng),小明有點(diǎn)心慌,又快速地點(diǎn)擊了兩下,提示下單成功。隨后小明到我的訂單列表中一看,發(fā)現(xiàn)有三個(gè)相同的訂單,小明一臉黑線。
 
什么是冪等性
HTTP/1.1中對(duì)冪等性的定義是:
Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.
這里不討論學(xué)術(shù)上如何定義冪等性,而是重點(diǎn)在于如何在分布式環(huán)境中提供對(duì)外冪等性的接口。對(duì)外提供的接口承諾冪等性,其要表達(dá)的含義是:只要調(diào)用接口成功,外部對(duì)接口的多次調(diào)用得到的結(jié)果是相同的。即執(zhí)行多次和一次的效果是一樣的。
為什么需要冪等
上面小明遇到的問(wèn)題,就是在防止重復(fù)提交的情況上沒(méi)有做好控制。業(yè)務(wù)開(kāi)發(fā)中,經(jīng)常會(huì)遇到重復(fù)提交的情況,無(wú)論是由于網(wǎng)絡(luò)問(wèn)題無(wú)法收到請(qǐng)求結(jié)果而重新發(fā)起請(qǐng)求,或是前端的操作抖動(dòng)而造成重復(fù)提交情況。
 在交易系統(tǒng),支付系統(tǒng)這種重復(fù)提交造成的問(wèn)題有尤其明顯,比如:
很顯然,冪等接口認(rèn)為,外部調(diào)用者會(huì)存在多次調(diào)用的場(chǎng)景,為了防止重試對(duì)數(shù)據(jù)狀態(tài)的改變,需要將接口的設(shè)計(jì)為冪等的。
什么情況下需要保證冪等性
以SQL為例,有下面三種場(chǎng)景,只有第三種場(chǎng)景需要開(kāi)發(fā)人員使用其他策略保證冪等性:
保證冪等策略
冪等需要通過(guò)唯一的業(yè)務(wù)單號(hào)來(lái)保證。也就是說(shuō)相同的業(yè)務(wù)單號(hào),認(rèn)為是同一筆業(yè)務(wù)。使用這個(gè)唯一的業(yè)務(wù)單號(hào)來(lái)確保,后面多次的相同的業(yè)務(wù)單號(hào)的處理邏輯和執(zhí)行效果是一致的。
 下面以支付為例,在不考慮并發(fā)的情況下,實(shí)現(xiàn)冪等很簡(jiǎn)單:①先查詢一下訂單是否已經(jīng)支付過(guò),②如果已經(jīng)支付過(guò),則返回支付成功;如果沒(méi)有支付,進(jìn)行支付流程,修改訂單狀態(tài)為‘已支付’。
防重復(fù)提交策略
上述的保證冪等方案是分成兩步的,第②步依賴第①步的查詢結(jié)果,無(wú)法保證原子性的。在高并發(fā)下就會(huì)出現(xiàn)下面的情況:第二次請(qǐng)求在第一次請(qǐng)求第②步訂單狀態(tài)還沒(méi)有修改為‘已支付狀態(tài)’的情況下到來(lái)。既然得出了這個(gè)結(jié)論,余下的問(wèn)題也就變得簡(jiǎn)單:把查詢和變更狀態(tài)操作加鎖,將并行操作改為串行操作。
樂(lè)觀鎖
如果只是更新已有的數(shù)據(jù),沒(méi)有必要對(duì)業(yè)務(wù)進(jìn)行加鎖,設(shè)計(jì)表結(jié)構(gòu)時(shí)使用樂(lè)觀鎖,一般通過(guò)version來(lái)做樂(lè)觀鎖,這樣既能保證執(zhí)行效率,又能保證冪等。例如:
 UPDATE tab1 SET col1=1,version=version+1 WHERE version=#version#
 不過(guò),樂(lè)觀鎖存在失效的情況,就是常說(shuō)的ABA問(wèn)題,不過(guò)如果version版本一直是自增的就不會(huì)出現(xiàn)ABA的情況。(從網(wǎng)上找了一張圖片很能說(shuō)明樂(lè)觀鎖,引用過(guò)來(lái),出自Mybatis對(duì)樂(lè)觀鎖的支持)
 
防重表
使用訂單號(hào)orderNo做為去重表的唯一索引,每次請(qǐng)求都根據(jù)訂單號(hào)向去重表中插入一條數(shù)據(jù)。第一次請(qǐng)求查詢訂單支付狀態(tài),當(dāng)然訂單沒(méi)有支付,進(jìn)行支付操作,無(wú)論成功與否,執(zhí)行完后更新訂單狀態(tài)為成功或失敗,刪除去重表中的數(shù)據(jù)。后續(xù)的訂單因?yàn)楸碇形ㄒ凰饕迦胧?#xff0c;則返回操作失敗,直到第一次的請(qǐng)求完成(成功或失敗)。可以看出防重表作用是加鎖的功能。
 
分布式鎖
這里使用的防重表可以使用分布式鎖代替,比如Redis。訂單發(fā)起支付請(qǐng)求,支付系統(tǒng)會(huì)去Redis緩存中查詢是否存在該訂單號(hào)的Key,如果不存在,則向Redis增加Key為訂單號(hào)。查詢訂單支付已經(jīng)支付,如果沒(méi)有則進(jìn)行支付,支付完成后刪除該訂單號(hào)的Key。通過(guò)Redis做到了分布式鎖,只有這次訂單訂單支付請(qǐng)求完成,下次請(qǐng)求才能進(jìn)來(lái)。相比去重表,將放并發(fā)做到了緩存中,較為高效。思路相同,同一時(shí)間只能完成一次支付請(qǐng)求。
 
token令牌
這種方式分成兩個(gè)階段:申請(qǐng)token階段和支付階段。
 第一階段,在進(jìn)入到提交訂單頁(yè)面之前,需要訂單系統(tǒng)根據(jù)用戶信息向支付系統(tǒng)發(fā)起一次申請(qǐng)token的請(qǐng)求,支付系統(tǒng)將token保存到Redis緩存中,為第二階段支付使用。
 第二階段,訂單系統(tǒng)拿著申請(qǐng)到的token發(fā)起支付請(qǐng)求,支付系統(tǒng)會(huì)檢查Redis中是否存在該token,如果存在,表示第一次發(fā)起支付請(qǐng)求,刪除緩存中token后開(kāi)始支付邏輯處理;如果緩存中不存在,表示非法請(qǐng)求。
 實(shí)際上這里的token是一個(gè)信物,支付系統(tǒng)根據(jù)token確認(rèn),你是你媽的孩子。不足是需要系統(tǒng)間交互兩次,流程較上述方法復(fù)雜。
 
支付緩沖區(qū)
把訂單的支付請(qǐng)求都快速地接下來(lái),一個(gè)快速接單的緩沖管道。后續(xù)使用異步任務(wù)處理管道中的數(shù)據(jù),過(guò)濾掉重復(fù)的待支付訂單。
 優(yōu)點(diǎn)是同步轉(zhuǎn)異步,高吞吐。不足是不能及時(shí)地返回支付結(jié)果,需要后續(xù)監(jiān)聽(tīng)支付結(jié)果的異步返回。
冪等性接口的不足
因此除了業(yè)務(wù)上的特殊要求外,盡量不提供冪等的接口。
總結(jié)
以上是生活随笔為你收集整理的高并发下的幂等策略分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
 
                            
                        - 上一篇: 2020-03-05
- 下一篇: SAP ECC 和 S4HANA Mat
