首頁 > 軟體

淺談Redis處理介面冪等性的兩種方案

2022-08-15 14:04:50

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

一、介面冪等性

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 表示式來登出查詢與刪除操作。

參考連結:

阿里面試官:介面的冪等性怎麼設計?

優雅地處理重複請求(並行請求)

SpringBoot 介面冪等性實現的 4 種方案!這個我真的服氣了!

到此這篇關於淺談Redis處理介面冪等性的兩種方案的文章就介紹到這了,更多相關Redis 介面冪等性內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com