Java進階知識點6:接口冪等性

1.冪等地定義

1.1數學定義

在數學裏,冪等有兩種主要的定義:

  • 在某二元運算下,冪等元素是指被自己重複運算(或對於函數是爲複合)的結果等於它自己的元素。如,乘法運算下,0和1符合的自乘運算符和冪等,即s*s=s
  • 某一元運算爲冪等的時,其作用在任一元素兩次後會和其作用一次的結果相同。例如,高斯符號便是冪等的,即f(f(x))=f(x)

1.2 計算機中的冪等

在計算機中,表示對同一個過程應用相同的參數多次和應用一次產生的效果是一樣,這樣的過程即被稱爲滿足冪等性
冪等:
update test_user set user_age = 25 where user_id = 2 ,這中情況無論執行多少次,結果都不受影響,所以是冪等的。
非冪等:
update test_user set user_times = user_times + 1 where user_id = 2, 這樣的更新語句每執行一次,結果都會不一樣,所以是非冪等的。

1.2.1Http規範定義

在HTTP/1.1規範中冪等性的定義是:

    A request method is considered "idempotent" if the intended  
    effect on the server of multiple identical requests with that     
    method is the same as the effect for a single such request. Of 
    the request methods defined by this specification, PUT, 
    DELETE, and safe request methods are idempotent.

即:
一個請求方法,如果被請求多次和被請求一次效果相同,被認爲是冪等的,比如PUT、DELETE和其他安全的請求方法都是冪等的。

1.2.2 微服務場景中冪等

由於微服務的普及,原有的單體應用,被設計成不同的功能模塊作爲服務,獨立部署在不同的物理環境中。要完成一個完整的業務流程,就需要在多個微服務中間進行調用,而調用的過程當然是經由網絡來完成的。
因此,網絡通信的不確定性因素,比如網絡的抖動,對端微服務的異常,可能會導致微服務間調用,產生超時的現象。爲保證業務流程的順利完成,調用過程必須建立重試機制。
然而,一旦建立了重試機制,那就可能會將同一個請求發送多次,導致接受方重複消費,多次執行相同操作,進而可能產生錯誤的數據。爲避免這個問題,必須保證可能產生錯誤數據的接口(方法),請求一次和請求多次的效果相同,即冪等。

2.需要冪等的場景

可能會發生重複請求或消費的場景,在微服務架構中是隨處可見的。以下是筆者梳理的幾個常見場景:

  • 網絡波動:
    因網絡波動,可能會引起重複請求

  • 分佈式消息消費:
    任務發佈後,使用分佈式消息服務來進行消費,參考【消息總線真的能保證冪等?】

  • 用戶重複操作:
    用戶在使用產品時,可能會誤操作而觸發多筆交易,或者因爲長時間沒有響應,而有意觸發多筆交易。

  • 未關閉的重試機制:
    技術人員人爲的錯誤,因開發人員、測試人員或運維人員沒有檢查出來,而開啓的重試機制(如Nginx重試、RPC通信重試或業務層重試等)

3.“天然”的冪等和需要“人工”的冪等

3.1CRUD分析

CRUD是指在做計算處理時的[增加](Create)、讀取(Read)、更新(Update)和刪除(Delete)幾個單詞的首字母簡寫。主要被用在描述軟件系統中數據庫或者持久層的基本操作功能。
所以CRUD角度分析冪等性,是從操作目的層面來看問題的:

操作 冪等性
新增類請求(C) 數據庫自增主鍵,不具備冪等性
查詢類動作(R) 重複查詢不會產生或變更新的數據,因此查詢是天然具備冪等性
基於主鍵的計算式更新(U) 不具備冪等性,即:UPDATE goods SET number=number-1 WHERE id=1
基於主鍵的非計算式更新(U) 具備冪等性,即:UPDATE goods SET number=newNumber WHERE id=1
基於條件查詢的更新(U) 不一定具有冪等性(需要根據實際情況進行分析判斷)
基於主建的刪除(D) 具備冪等性
業務層面都是邏輯刪除(即Update操作)(U) 不具備冪等性

3.2 HTTP方法分析

按照restful規範定義的接口,使用http方法,應該嚴格遵循http方法語義:

方法 冪等性 對應CRUD操作
POST 不安全且不冪等 C
GET 安全且冪等 R
PUT 不安全但冪等 U
DELETE 不安全但冪等 D

3.3 “天然”的冪等

GET,PUT,DELETE都是冪等操作,而POST不是,以下進行分析:
首先GET請求很好理解,對資源做查詢多次,此實現的結果都是一樣的。
PUT請求的冪等性可以這樣理解,將A修改爲B,它第一次請求值變爲了B,再進行多次此操作,最終的結果還是B,與一次執行的結果是一樣的,即屬於CURD中所說的基於主鍵的非計算式更新,所以PUT是冪等操作。
同理可以理解DELETE操作,第一次將資源刪除後,後面多次進行此刪除請求,最終結果是一樣的,將資源刪除掉了。

3.4需要“人工”的冪等

POST不是冪等操作,因爲一次請求添加一份新資源,二次請求則添加了兩份新資源,多次請求會產生不同的結果,因此POST不是冪等操作。
如果需要在POST方法的接口實現冪等,需要人爲加上冪等的機制。
下面我們來說說,冪等地實現方法。

4.冪等實現方法

4.1 全局唯一ID

如果使用全局唯一ID,就是根據業務的操作和內容生成一個全局ID,在執行操作前先根據這個全局唯一ID是否存在,來判斷這個操作是否已經執行。如果不存在則把全局ID,存儲到存儲系統中,比如數據庫、Redis等。如果存在則表示該方法已經執行。

使用全局唯一ID是一個通用方案,可以支持插入、更新、刪除業務操作。比較適用於有限制導入導出操作,比如:希望一個賬號在同一時間內只能導入或導出一次數據,這個時候就可以在導入前根據賬號建立一個對賬號唯一的key值放入redis中,執行業務方法時判斷是否存在,最後執行成功或失敗後刪除redis的Key.

但是這個方案看起來很美但是實現起來比較麻煩,下面的方案適用於特定的場景,但是實現起來比較簡單。

4.2 去重表

這種方法適用於在業務中有唯一標的插入場景中.

比如在以上的支付場景中,如果一個訂單隻會支付一次,所以訂單ID可以作爲唯一標識。這時,我們就可以建一張去重表,並且把唯一標識作爲唯一索引,用以記錄訂單支付信息,在我們實現時,把創建支付單據和寫入去去重表,放在一個事務中,如果重複創建,數據庫會拋出唯一約束異常,操作就會回滾。這個方法其實也是用到唯一ID,與上面全局唯一ID不同的是,他是針對具體單個業務流程的,實現起來相對簡單。

4.3 插入或更新

這種方法插入並且有唯一索引的情況,比如我們要關聯商品品類,其中商品的ID和品類的ID可以構成唯一索引,並且在數據表中也增加了唯一索引。這時就可以使用InsertOrUpdate操作。在mysql數據庫中如下:

insert into goods_category    
(goods_id,category_id,create_time,update_time) 
  values(#{goodsId},#{categoryId},now(),now()) 
on DUPLICATE KEY UPDATE update_time=now()

4.4 多版本控制

這種方法適合在更新的場景中,比如我們要更新商品的名字,這時我們就可以在更新的接口中增加一個版本號,來做冪等:

boolean updateGoodsName(int id,String newName,int version);

在實現時可以如下:

update goods set name=#{newName},version=#{version} where  
id=#{id} and version<${version}

4.5 狀態機控制

這種方法適合在有狀態機流轉的情況下,比如訂單的創建和付款,訂單的付款肯定是在之前,這時我們可以通過在設計狀態字段時,使用int類型,並且通過值類型的大小來做冪等,比如訂單的創建爲0,付款成功爲100,付款失敗爲99。在做狀態機更新時,我們就這可以這樣控制:

update goods_order set status=#{status} where id=#{id} and   
status<#{status}

以上就是保證接口冪等性的一些方法。

5.總結

冪等性設計不能脫離業務來討論,一般情況下,去重表同時也是業務數據表,而針對分佈式的去重ID,可以參考以下幾種方式:

  • UUID
  • Snowflake
  • 數據庫自增ID
  • 業務本身的唯一約束
  • 業務字段+時間戳拼接


-------------------------------------------------------------------
作者:撫劍聽琴
鏈接:https://www.jianshu.com/p/6707cbc4b294
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯繫作者獲得授權並註明出處。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章