後端程序員必須知道的接口冪等性

前言:

最近負責的項目出了一個問題,用戶操做回退失效。

本來的邏輯設計中,操做回退是須要回到操做前的狀態。前端

通過查看日誌發現,用戶以前的操做作了兩次,也就是說提交操做的接口被調用了兩次,致使用戶上一次的狀態和這一次的狀態是同樣的,因此操做回退是沒有問題的,問題出在了操做的接口被調用了兩次java

對於防止重複提交,通常是放在前端頁面控制的,用戶點擊完按鈕以後,後臺返回成功的結果,按鈕就不可見,實踐證實,客戶端的限制操做不是絕對可靠的。

針對上面的場景問題,進而引起了下文的內容;本文主線:redis

①、什麼是接口冪等性?數據庫

②、爲何會產生接口冪等性問題?後端

③、如何保證接口冪等性?瀏覽器

什麼是接口冪等性?

首先看看冪等性的概念:

冪等性本來是數學上的概念,用在接口上就能夠理解爲:同一個接口,屢次發出同一個請求,必須保證操做只執行一次。 調用接口發生異常而且重複嘗試時,老是會形成系統所沒法承受的損失,因此必須阻止這種現象的發生。服務器

好比下面這些狀況,若是沒有實現接口冪等性會有很嚴重的後果: 支付接口,重複支付會致使屢次扣錢 ;訂單接口,同一個訂單可能會屢次建立。網絡

圖片1;session

爲何會產生接口冪等性問題?

那麼,什麼狀況下,會產生接口冪等性的問題呢?下面簡要介紹下幾種狀況:多線程

  • 網絡波動, 可能會引發重複請求
  • 用戶重複操做,用戶在操做時候可能會無心觸發屢次下單交易,甚至沒有響應而有意觸發屢次交易應用
  • 使用了失效或超時重試機制(Nginx重試、RPC重試或業務層重試等)
  • 頁面重複刷新
  • 使用瀏覽器後退按鈕重複以前的頁面操做,致使重複提交表單
  • 使用瀏覽器歷史記錄重複提交表單
  • 瀏覽器重複的HTTP請求
  • 定時任務重複執行
  • 用戶雙擊提交按鈕
  • . . . . . .等等

如何保證接口冪等性?

那麼最關鍵的來了,如何保證接口冪等性?

解決辦法主要分爲兩個方向:

  • 一個方向是客戶端防止重複調用
  • 一個是服務端進行校驗

上面兩種方案,客戶端防止重複提交實現起來比較簡單,可是客戶端的限制操做不是絕對可靠,因此須要服務端同時進行校驗,雙重防禦實現絕對可靠。

下面就來聊聊保證接口冪等性的具體方案;
按鈕只可操做一次:

通常是提交後把按鈕置灰或loding狀態,消除用戶由於重複點擊而產生的重複記錄,好比添加操做,因爲點擊兩次而產生兩條記錄。

token機制:

功能上容許重複提交,但要保證重複提交不產生反作用,好比點擊n次只產生一條記錄,具體實現就是進入頁面時申請一個token,而後後面全部的請求都帶上這個token,後端根據token來避免重複請求。

圖片2;

使用Post/Redirect/Get模式:

在提交後執行頁面重定向,這就是所謂的Post-Redirect—Get(PRG)模式,簡單來講就是當用戶提交連表單後,跳轉到一個重定向的信息頁面,這樣就避免用戶按F5刷新致使的重複提交,並且也不會出現瀏覽器表單重複提交的警告,也能消除按瀏覽器前進和後退致使一樣重複提交的問題。

在session存放特殊標誌:

在服務端,生成一個惟一的標識符,將它存入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不一致致使更新失敗。

select + insert or update or delete :

該方案就是操做以前先查詢一下,符合要求再插入,該方案在沒有併發的系統中能夠解決冪等問題,在單JVM有併發的時候能夠用JVM加鎖來保證冪等性,在分佈式環境它是沒法保證冪等性,可使用分佈式鎖來保證。

分佈式鎖:

若是是分佈是系統,構建全局惟一索引比較困難,例如惟一性的字段無法肯定,這時候能夠引入分佈式鎖;

經過第三方的工具(redis或zookeeper),在業務系統插入數據或者更新數據,獲取分佈式鎖,而後作操做,以後釋放鎖,這樣實際上是把多線程併發的鎖的思路,引入多多個系統,也就是分佈式系統中得解決思路。

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

狀態機冪等:

在設計單據相關的業務,或者是任務相關的業務,確定會涉及到狀態機(狀態變動圖),就是業務單據上面有個狀態,狀態在不一樣的狀況下會發生變動;

通常狀況下存在有限狀態機,這時候,若是狀態機已經處於下一個狀態,這時候來了一個上一個狀態的變動,理論上是不可以變動的,這樣的話,保證了有限狀態機的冪等。

注意:訂單等單據類業務,存在很長的狀態流轉,必定要深入理解狀態機,對業務系統設計能力提升有很大幫助 。

防重表:

以支付爲例: 使用惟一主鍵去作防重表的惟一索引,好比使用訂單號做爲防重表的惟一索引;

每一次請求都根據訂單號向防重表中插入一條數據,插入成功說明能夠處理後面的業務,當處理完業務邏輯以後刪除防重表中的訂單號數據,後續若是有重複請求,則會由於防重表惟一索引緣由致使插入失敗,直接返回操做失敗,直到第一次請求返回結果;

能夠看出防重表做用就是加鎖的功能。 注: 最好結合狀態機冪等先判斷一下

緩衝隊列:

將請求都快速地接收下來後放入緩衝隊列中,後續使用異步任務處理隊列中的數據,過濾掉重複的請求,該解決方案優勢是同步處理改爲異步處理、高吞吐量,缺點則是不能及時地返回請求結果,須要後續輪詢得處理結果。

全局惟一號:

好比經過source來源 + 惟一序列號傳入給後端,後端來判斷請求是否重複,在併發時只能處理一個請求,其餘相同併發請求要麼返回請求重複,要麼等待 前面請求執行完成後再執行。

❤ 點贊 + 評論 + 轉發 喲

您能夠VX搜索【木子雷】公衆號,堅持高質量原創java技術文章,福利多多喲!

相關文章
相關標籤/搜索