1. 程式人生 > >由表單重復提交引發的冪等性思考

由表單重復提交引發的冪等性思考

思路 電商 完成後 cor 實現接口 延遲 策略 rom 是否

最近在本地開發測試的時候,遇到一個表單重復提交的現象。其實原因很簡單,因為網絡延遲的問題,我點擊了兩次提交按鈕,數據庫裏生成了兩條記錄。其實這種現象以前也有遇到過,一般都是提交後把按鈕置灰,無法再次提交,這是很常見的客戶端處理的方式。

但是這真的有從根本上解決問題嗎,雖然客戶端解決了多次提交的問題,但是接口中依舊存在著問題。假設我們不是從客戶端提交,而是被其他的系統調用,當遇到網絡延遲,系統補償的時候,是否也會遇到這種問題呢。看了下網上關於這類問題的解決方案,需要實現接口的冪等性。概念很高大上,結合我的實際的理解其實冪等性就是一個操作,不論執行多少次,產生的效果和返回的結果都是一樣的。

以我的實際案例來講,就是無論我點擊提交按鈕多少次,數據庫應該只有一條記錄才對。可能這個案例還不太符合冪等性的定義,再舉個我們都很切身的案例,當我們去參加一些電商的搶購活動,假設網絡卡頓,這時候很多人肯定會多次點擊支付按鈕,假設支付接口沒有做冪等性校驗,這時候會發生什麽情況,肯定會發生多次扣款的情況。

什麽時候需要實現冪等性接口?

在編程中.一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同。既然是這樣我們的查詢和刪除不就是多次執行的結果和一次執行的相同嗎。是的,查詢和刪除擁有天然的冪等性,當然刪除這個第一次執行和後面執行的返回值可能會有所不同,但是最終的效果是一致的。所以需要我們額外實現的冪等性接口主要是新增和更新操作

實現冪等性的技術方案

1、token機制,防止頁面重復提交

這種方法也是我目前在表單重復提交服務端的解決方案,技術原理很簡單。

這種方式分成兩個階段:申請token階段和執行新增操作。
第一階段,在進入到新增頁面之前,需要服務端發起一次申請token的請求,服務端一定的邏輯得出Token,並將token保存到Redis緩存中並設置生效時間,為第二階段使用,註意保證token的唯一性。

第二階段,新增頁面拿著申請到的token發起新增請求,服務端執行刪除token操作,如果返回0表示Token不存在,為非法請求,如果返回1則為第一次請求,發起新增請求。
註意:redis要用刪除操作來判斷token,刪除成功代表token校驗通過,如果用select+delete來校驗token,存在並發問題,不建議使用。

技術分享圖片

2、唯一索引,防止新增臟數據

比如:以上面搶購案例為例,我們點擊支付按鈕,向支付系統提交支付申請,這時候支付系統會將一條記錄插入到支付狀態表,這個表將支付的訂單號設為唯一索引,第一次請求將支付訂單記錄插入表中並設置為未支付同時返回給支付系統完成實際支付操作,後續重復請求就會因為唯一索引導致插入失敗而不會再走後續的實際支付操作。就好像一種另類的鎖機制。

3、悲觀鎖樂觀鎖機制

悲觀鎖樂觀鎖的用法,在我這些年的開發中應用還是比較廣泛的。
悲觀鎖,就是悲觀的認為數據會被改變,在數據修改的過程中始終是加鎖的。其他線程無論是讀還是寫都無法拿到數據。

select * from t_goods where id=1 for update;

與普通查詢不一樣的是,我們使用了select…for update的方式,這樣就通過數據庫實現了悲觀鎖。使用悲觀鎖的原理就是,當我們在查詢出goods信息後就把當前的數據鎖定,直到我們修改完畢後再解鎖。那麽在這個過程中,因為goods被鎖定了,就不會出現有第三者來對其進行修改了。

樂觀鎖,相對悲觀鎖來講更為廣泛一些,因為樂觀鎖不依賴數據庫,只會在update的一瞬間加鎖,其余處理過程中並不加鎖。

UPDATE t_goods
SET STATUS = #{status},name=#{name},version=version+1 
WHERE
    id = #{id} and version=#{version} 

樂觀鎖每次只會有一個線程執行成功,其他線程因為條件發生了改變,而執行失敗,這樣就避免了數據覆蓋的可能性。

樂觀鎖悲觀鎖雖然很好的解決了數據不一致的問題,但是也要學會善用。因為數據庫的鎖機制,條件字段一定要是主鍵或者唯一索引,不然會造成鎖表或者鎖無限的可能,可以說印象深刻,我就曾因為這個被狠批過。o(╥﹏╥)o

如果有同學不了解悲觀鎖和樂觀鎖的,可以看下這兩篇深入了解下。後面我也會梳理下mysql的鎖機制總結分享出來,目前自己對數據庫鎖機制也是一知半解。

》》》使用mysql樂觀鎖解決並發問題

》》》使用mysql悲觀鎖解決並發問題

4、 分布式鎖

可以用redis或zookeeper實現分布式鎖。以電商支付為案例,訂單發起支付請求,支付系統首先查詢訂單是否已經支付,如果已經支付,直接返回已支付。如果未支付去Redis緩存中查詢是否存在該訂單號的Key,如果不存在,則向Redis增加Key為訂單號,已存在返回重復操作。再次查詢是否完成支付,如果沒有則進行支付,支付完成後刪除該訂單號的Key。通過Redis做到了分布式鎖,只有這筆訂單支付請求完成,下次請求才能進來。相比唯一索引,將並發放到了緩存中,較為高效。思路相同,同一時間只能完成一次支付請求。

要點:某個長流程處理過程要求不能並發執行,可以在流程執行之前根據某個標誌(用戶ID+後綴等)獲取分布式鎖,其他流程執行時獲取鎖就會失敗,也就是同一時間該流程只能有一個能執行成功,執行完成後,釋放分布式鎖。

分布式鎖我並沒有親手實踐過,相關的原理也是從大佬那裏偷師來的,後續親身實踐過會做不定期更新。

站在大佬的肩膀上,才能更快的撬動地球~

參考:

高並發的核心技術-冪等的實現方案

冪等策略分析

由表單重復提交引發的冪等性思考