由表單重複提交引起的冪等性思考

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

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

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

何時須要實現冪等性接口?

 在編程中.一個冪等操做的特色是其任意屢次執行所產生的影響均與一次執行的影響相同。既然是這樣咱們的查詢和刪除不就是屢次執行的結果和一次執行的相同嗎。是的,查詢和刪除擁有自然的冪等性,固然刪除這個第一次執行和後面執行的返回值可能會有所不一樣,可是最終的效果是一致的。因此須要咱們額外實現的冪等性接口主要是新增和更新操做sql

實現冪等性的技術方案

一、token機制,防止頁面重複提交 

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

這種方式分紅兩個階段:申請token階段和執行新增操做。
第一階段,在進入到新增頁面以前,須要服務端發起一次申請token的請求,服務端必定的邏輯得出Token,並將token保存到Redis緩存中並設置生效時間,爲第二階段使用,注意保證token的惟一性。
第二階段,新增頁面拿着申請到的token發起新增請求,服務端執行刪除token操做,若是返回0表示Token不存在,爲非法請求,若是返回1則爲第一次請求,發起新增請求。
注意:redis要用刪除操做來判斷token,刪除成功表明token校驗經過,若是用select+delete來校驗token,存在併發問題,不建議使用。編程

 二、惟一索引,防止新增髒數據 

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

 三、悲觀鎖樂觀鎖機制

悲觀鎖樂觀鎖的用法,在我這些年的開發中應用仍是比較普遍的。
悲觀鎖,就是悲觀的認爲數據會被改變,在數據修改的過程當中始終是加鎖的。其餘線程不管是讀仍是寫都沒法拿到數據。網絡

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悲觀鎖解決併發問題

四、 分佈式鎖

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

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

分佈式鎖我並無親手實踐過,相關的原理也是從大佬那裏偷師來的,後續親身實踐事後會作不按期更新。

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

參考:

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

冪等策略分析

相關文章
相關標籤/搜索