最近負責的項目出了一個問題,用戶操做回退失效。
本來的邏輯設計中,操做回退是須要回到操做前的狀態。前端
通過查看日誌發現,用戶以前的操做作了兩次,也就是說提交操做的接口被調用了兩次,致使用戶上一次的狀態和這一次的狀態是同樣的,因此操做回退是沒有問題的,問題出在了操做的接口被調用了兩次 。java
對於防止重複提交,通常是放在前端頁面控制的,用戶點擊完按鈕以後,後臺返回成功的結果,按鈕就不可見,實踐證實,客戶端的限制操做不是絕對可靠的。
針對上面的場景問題,進而引起了下文的內容;本文主線:redis
①、什麼是接口冪等性?數據庫
②、爲何會產生接口冪等性問題?後端
③、如何保證接口冪等性?瀏覽器
首先看看冪等性的概念:
冪等性本來是數學上的概念,用在接口上就能夠理解爲:同一個接口,屢次發出同一個請求,必須保證操做只執行一次。 調用接口發生異常而且重複嘗試時,老是會形成系統所沒法承受的損失,因此必須阻止這種現象的發生。服務器
好比下面這些狀況,若是沒有實現接口冪等性會有很嚴重的後果: 支付接口,重複支付會致使屢次扣錢 ;訂單接口,同一個訂單可能會屢次建立。網絡
圖片1;session
那麼,什麼狀況下,會產生接口冪等性的問題呢?下面簡要介紹下幾種狀況:多線程
那麼最關鍵的來了,如何保證接口冪等性?
解決辦法主要分爲兩個方向:
上面兩種方案,客戶端防止重複提交實現起來比較簡單,可是客戶端的限制操做不是絕對可靠,因此須要服務端同時進行校驗,雙重防禦實現絕對可靠。
下面就來聊聊保證接口冪等性的具體方案;
通常是提交後把按鈕置灰或loding狀態,消除用戶由於重複點擊而產生的重複記錄,好比添加操做,因爲點擊兩次而產生兩條記錄。
功能上容許重複提交,但要保證重複提交不產生反作用,好比點擊n次只產生一條記錄,具體實現就是進入頁面時申請一個token,而後後面全部的請求都帶上這個token,後端根據token來避免重複請求。
圖片2;
在提交後執行頁面重定向,這就是所謂的Post-Redirect—Get(PRG)模式,簡單來講就是當用戶提交連表單後,跳轉到一個重定向的信息頁面,這樣就避免用戶按F5刷新致使的重複提交,並且也不會出現瀏覽器表單重複提交的警告,也能消除按瀏覽器前進和後退致使一樣重複提交的問題。
在服務端,生成一個惟一的標識符,將它存入session,同時前端獲取這個標識符的值將它寫入表單的隱藏中,用於用戶輸入信息後點擊一塊兒提交,在服務器端,獲取表單中隱藏字段的值,與session中的惟一標識符比較,相等說明是首次提交,就處理本次請求,而後將session中的惟一標識符移除,不相等則表示是重複提交,再也不作處理。
利用數據庫惟一索引機制,當數據重複時,插入數據庫會拋出異常,保證不會出現髒數據。
若是更新已有數據,能夠進行加鎖更新,也能夠設計表結構時使用樂觀鎖,經過version來作樂觀鎖,這樣既能保證執行效率,又能保證冪等;
樂觀鎖的version版本在更新業務數據要自增 update table set version = version + 1 where id = #{id} and version = #{version} ;
當有重複請求的時候,第一個請求會獲取當前商品的version版本號,獲得的version爲1,緊接着因爲第一個請求還沒更新商品的version,第二個請求獲取的version依然也是1 ;
這時候第一個請求操做更新的時候帶上version並做爲條件而且自增更新,這時候商品的version就會變成2,當第二個請求去操做更新的時候明顯version不一致致使更新失敗。
該方案就是操做以前先查詢一下,符合要求再插入,該方案在沒有併發的系統中能夠解決冪等問題,在單JVM有併發的時候能夠用JVM加鎖來保證冪等性,在分佈式環境它是沒法保證冪等性,可使用分佈式鎖來保證。
若是是分佈是系統,構建全局惟一索引比較困難,例如惟一性的字段無法肯定,這時候能夠引入分佈式鎖;
經過第三方的工具(redis或zookeeper),在業務系統插入數據或者更新數據,獲取分佈式鎖,而後作操做,以後釋放鎖,這樣實際上是把多線程併發的鎖的思路,引入多多個系統,也就是分佈式系統中得解決思路。
要點:某個長流程處理過程要求不能併發執行,能夠在流程執行以前根據某個標誌(用戶ID+後綴等)獲取分佈式鎖,其餘流程執行時獲取鎖就會失敗,也就是同一時間該流程只能有一個能執行成功,執行完成後,釋放分佈式鎖。
在設計單據相關的業務,或者是任務相關的業務,確定會涉及到狀態機(狀態變動圖),就是業務單據上面有個狀態,狀態在不一樣的狀況下會發生變動;
通常狀況下存在有限狀態機,這時候,若是狀態機已經處於下一個狀態,這時候來了一個上一個狀態的變動,理論上是不可以變動的,這樣的話,保證了有限狀態機的冪等。
注意:訂單等單據類業務,存在很長的狀態流轉,必定要深入理解狀態機,對業務系統設計能力提升有很大幫助 。
以支付爲例: 使用惟一主鍵去作防重表的惟一索引,好比使用訂單號做爲防重表的惟一索引;
每一次請求都根據訂單號向防重表中插入一條數據,插入成功說明能夠處理後面的業務,當處理完業務邏輯以後刪除防重表中的訂單號數據,後續若是有重複請求,則會由於防重表惟一索引緣由致使插入失敗,直接返回操做失敗,直到第一次請求返回結果;
能夠看出防重表做用就是加鎖的功能。 注: 最好結合狀態機冪等先判斷一下
將請求都快速地接收下來後放入緩衝隊列中,後續使用異步任務處理隊列中的數據,過濾掉重複的請求,該解決方案優勢是同步處理改爲異步處理、高吞吐量,缺點則是不能及時地返回請求結果,須要後續輪詢得處理結果。
好比經過source來源 + 惟一序列號傳入給後端,後端來判斷請求是否重複,在併發時只能處理一個請求,其餘相同併發請求要麼返回請求重複,要麼等待 前面請求執行完成後再執行。
您能夠VX搜索【木子雷】公衆號,堅持高質量原創java技術文章,福利多多喲!