訂單重復(fù)問題已經(jīng)是老生常談的問題了,我們熟悉的淘寶購物流程,購物車-->生成訂單--》提交訂單--》訂單確認(rèn)--》支付訂單--》備貨--》發(fā)貨,這里可能會有一些問題,例如,有人惡意或者無意的重復(fù)提交訂單,從而導(dǎo)致數(shù)據(jù)庫保存的訂單數(shù)量與實際不符,重復(fù)提交訂單導(dǎo)致用戶體驗不佳,甚至更為嚴(yán)重的是,用戶重復(fù)支付了同一個訂單。所以,涉及到訂單,應(yīng)該首先想清楚如何設(shè)計才能保證系統(tǒng)不會出現(xiàn)這些問題
如何防止訂單重復(fù)提交
首先說兩個我們購物時經(jīng)常有過的體驗或者說購物網(wǎng)站的網(wǎng)頁提醒
- 你提交的動作過快,請稍后嘗試
- 你的訂單已經(jīng)超時,請刷新頁面后重新提交
看到這些提示,說明該購物網(wǎng)站做了訂單提交的限制,一方面是防止有人惡意無限制提交訂單,所以限制了一定時間內(nèi)最大可操作次數(shù),另一方面是為了保證訂單無重復(fù)提交。那么這是怎么做到的呢?
第一個應(yīng)該比較簡單,限制某個時間內(nèi)的最大操作次數(shù)只需要有一個計數(shù)器就可以,計數(shù)器可以用redis實現(xiàn),設(shè)置一個帶有有效時間的值作為計數(shù)器,如果值不存在則自動創(chuàng)建,超過某一個值就認(rèn)為操作次數(shù)用完即可以實現(xiàn)。
第二個可以使用token機(jī)制,token即令牌,學(xué)過spring security的相信對這個詞不會陌生。我們可以使用類似spring security的機(jī)制在頁面上生成一個token,當(dāng)提交訂單時,根據(jù)該token的有效時間和允許的使用次數(shù)來判斷訂單是否允許提交,從而規(guī)避重復(fù)提交的問題。當(dāng)然,有人會問,在高并發(fā)的情況下,如果是判斷token有效之前有很多同一個用戶的提交線程過來(用戶正常使用一般不會出現(xiàn)這種情況,一般是壓力測試工具導(dǎo)致的),那么還是會重復(fù)提交,所以,這里需要用到鎖機(jī)制,訪問同一個用戶的token同一時間只能有一個線程,token使用之后失效了就會被清掉,之后的線程就找不到該token,從而認(rèn)為訂單不能提交。
訂單確認(rèn)支付
如支付寶和微信等,支付寶和微信本身是怎么限制訂單只能支付一次的呢?訂單怎么保證只會被支付一次呢?這個問題相對來說就簡單很多了,同一訂單的狀態(tài)更新的SQL只需要帶上條件,利用的是數(shù)據(jù)庫的行鎖。當(dāng)然,如果是分布式系統(tǒng),這里涉及到的問題會更多。
update table item set item.status=:newstatus where item.id = :id and item.status = oldstatus.
對比案例
-
美團(tuán)GTIS
主要看GTIS的流程圖以及想想其交易ID的作用,交易之前,后臺會返回一個交易ID給前端,前端在點擊交易按鈕時需將該交易ID和其他交易信息同時返回給后臺進(jìn)行處理,通過全局的交易ID實現(xiàn)“該次交易的”冪等性
美團(tuán)GTIS.png
參考資料和研究
- 分布式鎖的對比分析:非常好的一篇文章,包含下面的關(guān)鍵詞:可重入、阻塞、公平、排他、樂觀、悲觀、單點、死鎖發(fā)生、連接池狀況、實現(xiàn)種類、實現(xiàn)方式