博主負責的項目報了一個問題,用戶操做回退失效。咱們的設計裏,操做回退是回到操做前的狀態。通過查看日誌發現,用戶以前的操做作了兩次,也就是說提交操做的接口被調用了兩次,致使之用戶上一次的狀態和這一次的狀態是同樣的,因此操做回退是沒有問題的,問題出在了操做的接口被調用了兩次。html
對於防止重複提交,是放在前端控制的,用戶點擊完按鈕以後,後臺返回成功的結果,按鈕就不可見,實踐證實,客戶端的限制操做不是絕對可靠的。前端
針對上面的場景,就引入了今天的問題,什麼是接口冪等性?如何保證接口冪等性?redis
首先看看冪等性的概念:數據庫
冪等性本來是數學上的概念,用在接口上就能夠理解爲:同一個接口,屢次發出同一個請求,必須保證操做只執行一次。 調用接口發生異常而且重複嘗試時,老是會形成系統所沒法承受的損失,因此必須阻止這種現象的發生。後端
好比下面這些狀況,若是沒有實現接口冪等性會有很嚴重的後果: 支付接口,重複支付會致使屢次扣錢 ;訂單接口,同一個訂單可能會屢次建立。瀏覽器
那麼,什麼狀況下,會產生接口冪等性的問題呢?服務器
那麼最關鍵的來了,如何保證接口冪等性?網絡
解決辦法分爲兩個方向,一個方向是客戶端防止重複調用,一個是服務端進行校驗。固然,客戶端防止重複提交併非絕對可靠的,優勢是實現起來比較簡單。session
通常是提交後把按鈕置灰或loding狀態,消除用戶由於重複點擊而產生的重複記錄,好比添加操做,因爲點擊兩次而產生兩條記錄多線程
功能上容許重複提交,但要保證重複提交不產生反作用,好比點擊n次只產生一條記錄,具體實現就是進入頁面時申請一個token,而後後面全部的請求都帶上這個token,後端根據token來避免重複請求。
在提交後執行頁面重定向,這就是所謂的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來源 + 惟一序列號傳入給後端,後端來判斷請求是否重複,在併發時只能處理一個請求,其餘相同併發請求要麼返回請求重複,要麼等待 前面請求執行完成後再執行。
參考:
【1】:什麼是接口的冪等性,如何實現接口冪等性?一文搞定
【2】:分佈式系統中接口的冪等性
【3】:高併發下接口冪等性解決方案