首頁  >  文章  >  資料庫  >  簡單聊聊Redis處理介面冪等性的兩種方案

簡單聊聊Redis處理介面冪等性的兩種方案

WBOY
WBOY轉載
2022-08-18 17:57:112450瀏覽

推薦學習:Redis影片教學

#前言:介面冪等性問題,對於開發人員來說,是一個跟語言無關的公共問題。對於某些使用者請求,在某些情況下是可能重複發送的,如果是查詢類操作並無大礙,但其中有些是涉及寫入操作的,一旦重複了,可能會導致很嚴重的後果,例如交易的介面如果重複請求可能會重複下單。介面冪等性是指使用者對於同一操作發起的一次請求或多次請求的結果是一致的,不會因為多次點擊而產生了副作用。

一、介面冪等性

1.1、什麼是介面冪等性

在HTTP/1.1中,對冪等性進行了定義。它描述了一次和多次請求某一個資源對於資源本身應該具有同樣的結果,即第一次請求的時候對資源產生了副作用,但是以後的多次請求都不會再對資源產生副作用。這裡的副作用是不會對結果產生破壞或產生不可預料的結果。也就是說,其任意多次執行對資源本身所產生的影響均與一次執行的影響相同。

這類問題多於介面的:

  • insert操作,這種情況下多次要求,可能會產生重複資料。
  • update操作,如果只是單純的更新數據,例如:update user set status=1 where id=1,是沒有問題的。如果還有計算,例如:update user set status=status 1 where id=1,這種情況下多次要求,可能會導致資料錯誤。

1.2、為什麼需要實作冪等性

在介面呼叫時一般情況下都能正常回傳資訊不會重複提交,不過在遇見以下情況時可以就會出現問題,如:

  • 前端重複提交表單: 在填寫一些表格時候,使用者填寫完成提交,很多時候會因為網路波動而沒有及時對使用者做出提交成功回應,致使用戶認為沒有成功提交,然後一直點提交按鈕,這時就會發生重複提交表單請求。
  • 用戶惡意進行刷單: 例如在實現用戶投票此功能時,如果用戶針對一個用戶進行重複提交投票,這會導致介面接收到用戶重複提交的投票訊息,這樣會使投票結果與事實嚴重不符。
  • 介面逾時重複提交: 很多時候HTTP 用戶端工具都預設開啟逾時重試的機制,尤其是第三方呼叫介面時候,為了防止網路波動逾時等造成的請求失敗,都會新增重試機制,導致一個請求提交多次。
  • 訊息進行重複消費: 當使用 MQ 訊息中間件時候,如果發生訊息中間件出現錯誤未及時提交消費訊息,導致發生重複消費。

本文討論的是如何在服務端優雅地統一處理這種接口冪等性情況,如何禁止用戶重複點擊等客戶端操作不在此次討論範圍。

1.3、引入冪等性後對系統的影響

冪等性是為了簡化客戶端邏輯處理,能放置重複提交等操作,但卻增加了服務端的邏輯複雜性與成本,其主要是:

  • 把並行執行的功能改為串列執行,降低了執行效率。
  • 增加了額外控制冪等的業務邏輯,複雜化了業務功能;

所以在使用時候需要考慮是否引入冪等性的必要性,根據實際業務場景具體分析,除了業務上的特殊要求外,一般情況下不需要引入的介面冪等性。

二、如何設計冪等

冪等意味著一條請求的唯一性。不管是你哪個方案去設計冪等,都需要一個全域唯一的ID ,去標記這個請求是獨一無二的。

  • 如果你是利用唯一索引控制冪等,那唯一索引是唯一的
  • 如果你是利用資料庫主鍵控制冪等,那主鍵是唯一的
  • 如果你是悲觀鎖的方式,底層標記還是全域唯一的ID

2.1、全域的唯一性ID

全域唯一性ID,我們要怎麼去生成呢?你可以回想下,資料庫主鍵Id怎麼產生的呢?

是的,我們可以使用UUID,但是UUID的缺點比較明顯,它字串佔用的空間比較大,產生的ID過於隨機,可讀性差,而且沒有遞增。

我們也可以使用雪花演算法(Snowflake) 產生唯一性ID。

雪花演算法是一種產生分散式全域唯一ID的演算法,所產生的ID稱為Snowflake IDs。這種演算法由Twitter創建,並用於推文的ID。

一個Snowflake ID有64位。

  • 第1位:Java中long的最高位是符號位代表正負,正數是0,負數是1,一般產生ID都是正數,所以預設為0。
  • 接下來前41位是時間戳,表示了自選定的時期以來的毫秒數。
  • 接下來的10位元代表電腦ID,防止衝突。
  • 其餘12位元代表每台機器上產生ID的序號,這允許在同一毫秒內建立多個Snowflake ID。

 當然,全域唯一性的ID,還可以使用百度的Uidgenerator,或是美團的Leaf# 。

2.2、冪等設計的基本流程

冪等處理的過程,說到底其實就是過濾一下已經收到的請求,當然,請求一定要有一個全域唯一的ID標記哈。然後,要怎麼判斷請求是否之前收到過呢?把請求儲存起來,收到請求時,先查下儲存記錄,記錄存在就回傳上次的結果,不存在就處理請求。

一般的冪等處理就是這樣,如下:

#三、介面冪等性常見解決方案

3.1 、下游傳遞唯一請求編號

可能會想到的是,只要請求有唯一的請求編號,那麼就能藉用Redis做這個去重——只要這個唯一請求編號在Redis存在,證明處理過,那麼就認為是重複的。

方案描述:

所謂唯一請求序號,其實就是每次向服務端請求時候附帶一個短時間內唯一不重複的序號,該序號可以是一個有序ID,也可以是一個訂單號,一般由下游生成,在呼叫上游服務端介面時附加該序號和用於認證的ID。

當上游伺服器收到請求資訊後拿取該序號和下游認證ID 進行組合,形成用於操作Redis 的Key,然後到Redis 中查詢是否存在對應的Key 的鍵值對,根據其結果:

  • 如果存在,就說明已經對該下游的該序號的請求進行了業務處理,這時可以直接回應重複請求的錯誤訊息。
  • 如果不存在,就以該Key 作為Redis 的鍵,以下游關鍵資訊作為儲存的值(例如下游商傳遞的一些業務邏輯資訊),將該鍵值對儲存到Redis 中,然後再正常執行對應的業務邏輯即可。

適用操作:

  • 插入操作
  • 更新操作
  • 刪除操作
##使用限制:

    要求第三方傳遞唯一序號;
  • 需要使用第三方元件Redis 進行資料效驗;
主要流程:

 主要步驟:

     下游服務產生分散式ID 作為序號,然後執行請求呼叫上游接口,並附帶「唯一序號」與請求的「認證憑證ID」。
  •  上游服務進行安全效驗,偵測下游傳遞的參數中是否存在「序號」和「憑證ID」。
  •  上游服務到 Redis 中檢測是否存在對應的「序號」與「認證ID」組成的 Key,如果存在就拋出重複執行的異常訊息,然後回應下游對應的錯誤訊息。如果不存在就以該「序號」和「認證ID」組合作為 Key,以下游關鍵資訊作為 Value,進而儲存到 Redis 中,然後正常執行接來的業務邏輯。
上面步驟中插入資料到 Redis 一定要設定過期時間。這樣能保證在這個時間範圍內,如果重複呼叫接口,則能夠進行判斷識別。如果不設定過期時間,很可能導致資料無限量的存入 Redis,致使 Redis 無法正常運作。

3.2、防重Token 令牌

方案描述:

針對客戶端連續點選或呼叫方的逾時重試等情況,例如提交訂單,此種操作就可以用Token 的機制實現防止重複提交。簡單的說就是呼叫方在呼叫介面的時候先向後端請求一個全域ID(Token),請求的時候攜帶這個全域ID 一起請求(Token 最好將其放到Headers 中),後端需要對這個Token作為Key,使用者資訊作為Value 到Redis 中進行鍵值內容校驗,如果Key 存在且Value 匹配就執行刪除命令,然後正常執行後面的業務邏輯。如果不存在對應的 Key 或 Value 不匹配就回傳重複執行的錯誤訊息,這樣來確保冪等運算。

使用限制:

  • 需要產生全域唯一Token 字串;
  • 需要使用第三方元件Redis 進行資料效驗;

主要流程:

  • 服務端提供取得Token 的接口,該Token 可以是一個序號,也可以是一個分散式ID 或UUID 字串。

  • 客戶端呼叫介面取得 Token,這時候服務端會產生一個 Token 字串。

  • 然後將該字串存入 Redis 資料庫中,以該 Token 作為 Redis 的鍵(注意設定過期時間)。

  • 將 Token 返回客戶端,客戶端拿到後應存到表單隱藏域。

  • 客戶端執行提交表單時,把 Token 存入 Headers 中,執行業務請求帶上該 Headers。

  • 服務端接收到請求後從 Headers 拿到 Token,然後根據 Token 到 Redis 中尋找該 key 是否存在。

  • 服務端根據 Redis 中是否存該 key 進行判斷,如果存在就將該 key 刪除,然後正常執行業務邏輯。如果不存在就拋異常,傳回重複提交的錯誤訊息。

注意,在並發情況下,執行 Redis 尋找資料與刪除需要保證原子性,否則很可能在並發下無法保證冪等性。其實作方法可以使用分散式鎖定或使用 Lua 表達式註銷查詢與刪除操作。

推薦學習:Redis影片教學

以上是簡單聊聊Redis處理介面冪等性的兩種方案的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:jb51.net。如有侵權,請聯絡admin@php.cn刪除