什麼是冪等性
- 冪等性定義:
- 一次和多次請求某一個資源對於資源本身應該具有同樣的結果
- 任意多次執行對資源本身所產生的影響均與一次執行的影響相同
- 冪等性定義的幾個重點:
- 冪等不僅僅只是一次或者多次請求對資源沒有副作用
- 比如,查詢資料庫操作,沒有增刪改,無論多少次操作對資料庫都沒有任何影響
- 冪等還包括第一次請求的時候對資源產生了副作用,但是以後的多次請求都不會再對資源產生副作用
- 冪等關注的是以後多次請求是否對資源產生副作用,並不關注結果
- 網路超時等問題,不是冪等的討論範圍
- 冪等不僅僅只是一次或者多次請求對資源沒有副作用
- 冪等性是系統服務對外一種承諾,而不是實現
- 承諾只要呼叫介面成功,外部多次呼叫對系統的影響是一致的
- 宣告為冪等的服務會認為外部呼叫失敗是常態,並且失敗後必然會有重試
冪等性的使用場景
- 業務開發中,經常遇到重複提交的情況:
- 由於網路問題無法收到請求結果而重新發起請求
- 前端的操作抖動而造成的重複提交的情況
- 在交易系統中,支付系統這種重複提交造成的問題尤為明顯:
- 使用者在APP上連續點選多次提交訂單,後臺應該只產生一個訂單
- 向支付系統發起請求,由於網路問題或者系統Bug問題導致重發,支付系統應該只做一次扣除操作
- 宣告冪等的服務認為,外部呼叫者會存在多次呼叫的情況,為了防止外部多次呼叫對系統的資料狀態發生多次改變,需要將服務設計為冪等
冪等和防重
- 重複提交的情況和服務冪等的初衷是不同的
- 重複提交是在第一次請求已經成功的情況下 ,人為地進行多次操作, 導致不滿足冪等要求的服務多次改變狀態
- 冪等更多使用的情況是第一次請求因為某些情況,不如超時,而導致不知道結果或者請求失敗的異常情況下,發起多次請求
- 冪等的目的是請求多次確認第一次請求成功,不會因為多次請求而出現多次的狀態變化
保證冪等性的情況
- 在SQL中,有以下三種場景,只有第三種場景需要保證冪等性:
- SELECT col1 FROM tab1 WHERE col2=2 : 無論執行多少次都不會改變狀態,是天然的冪等
- UPDATE tab1 SET col1=1 WHERE col2=2 : 無論執行成功多少次狀態都是一致的,也是冪等操作
- UPDATE tab1 SET col1=col1+1 WHERE col2=2: 每次執行的結果都會發生變化,這種不是冪等的,要採取策略保證冪等性
設計冪等性服務
- 冪等使得客戶端邏輯處理很簡單,但是服務端邏輯會很複雜
- 滿足冪等性服務需要包含兩點邏輯:
- 首先去查詢上一次的執行狀態,如果沒有則認為是第一次請求
- 在服務改變狀態的業務邏輯前保證防重複提交的邏輯
保證冪等策略
- 冪等需要通過唯一的業務單號來保證:
- 相同的業務單號,認為是同一業務
- 使用唯一的業務單號確保:後面多次相同業務單號的處理邏輯和執行效果是一致的
- 冪等實現示例-支付:
- 先查詢訂單是否支付過
- 如果已經支付過,返回支付成功
- 如果沒有支付,則進行支付流程,修改訂單的狀態為已支付
防重複提交策略
- 在保證冪等的策略中,執行是分兩步執行的,後面一步依賴上面一步的查詢結果,這樣就無法保證原子性
- 無法保證原子性在高併發的情況下會存在問題:
- 第二次請求在第一次請求的下一步訂單狀態沒有修改為"已支付狀態"時進行
- 為了解決這個問題 :將查詢和變更狀態操作加鎖,並將並行操作改為序列執行
樂觀鎖
- 如果只是更新已有的資料,沒有必要對業務進行加鎖
- 設計表結構時使用樂觀鎖,一般通過version來實現樂觀鎖:
- 保證執行效率
- 保證冪等
UPDATE tab1
SET col1=1,version=version+1
WHERE version=#version#
由於ABA問題會導致樂觀鎖存在失效的情況,只要保證version值自增就不會出現ABA的問題
防重表
- 使用orderNo作為去重表中的唯一索引,每次請求都根據訂單號orderNo向去重表中插入一條資料:
- 第一次請求查詢訂單支付狀態:
- 訂單沒有支付
- 進行支付操作
- 無論成功與否,執行完成之後更新訂單的狀態為成功或失敗,刪除去重表中的資料
- 後續訂單因為表中的唯一索引插入失敗,返回操作失敗,直到第一次請求完成(成功或者失敗)
- 第一次請求查詢訂單支付狀態:
- 防重表的作用是實現加鎖的功能
分散式鎖
- 可以使用Redis分散式鎖代替防重表的功能
- 示例:
- 訂單發起支付請求
- 支付系統會去Redis快取中查詢是否存在該訂單Key
- 如果不存在,向Redis中增加Key為訂單號
- 查詢訂單支付是否已經支付
- 如果沒有則進行支付,支付完成後刪除該訂單的Key
- 通過Redis實現分散式鎖,只有這次訂單請求完成,下次請求才會進來
- 對比去重表,Redis分散式鎖將放併發做在快取中,效率更高
- 同一時間只能完成一次支付請求
token令牌
- token令牌分為兩個階段:
- 申請token階段:
- 在進入到提交訂單頁面之前,需要訂單系統根據使用者資訊向支付系統發起一次申請token的請求
- 支付系統將token儲存到Redis快取中,給支付階段使用
- 支付階段:
- 訂單系統獲取到申請的token, 發起支付請求,
- 支付系統檢查Redis是否存在該token
- 如果存在,表示第一次發起支付請求,刪除快取中的token開始支付邏輯處理
- 如果快取中不存在,表示非法請求
- 申請token階段:
支付緩衝區
- 支付緩衝區:
- 將訂單的支付請求都快速地接收下來,是一個快速接收請求的緩衝管道
- 使用非同步任務處理管道中的資料,過濾調掉重複的待支付的資料
- 優點: 同步轉非同步,高吞吐
- 缺點: 無法及時返回支付結果,需要後續監聽支付結果的非同步返回
冪等的不足:
- 冪等是為了簡化客戶端邏輯,但是增加了服務提供者的邏輯和成本
- 冪等的使用需要根據具體場景具體分析
- 增加了額外控制冪等的業務邏輯,複雜了業務功能
- 將並行的功能轉化為序列,降低了執行效率