高併發下如何輕鬆的保證接口冪等性
日常的開發中有些接口對冪等性有嚴格的要求,如增加 / 扣減積分、用戶支付 / 退款等場景,如果沒有做接口的冪等性就會造成一定的資損或者用戶投訴等問題。如下是增加積分過程,若接口未做冪等處理,現在由於積分服務響應超時導致 Nginx 重試:
會出現由於積分服務沒有做接口冪等處理,Nginx 重試操作使得積分接口多次被調用,最終會給用戶多加了積分。
那麼導致接口重複執行的來源有哪些?如何保證接口的冪等性呢?下面我們就這些問題做分析。
1、接口重複執行的場景
(1)用戶重複提交請求(如用戶點擊按鈕手速快,連續點擊了多次提交按鈕)或者用戶的惡意攻擊,導致請求被多次的轉發到後端服務上。如下是用戶連續點擊發送的兩次請求到後端:
實際上請求 1 和請求 2 我們只需要處理一個請求就好了,但是現在兩個請求都過來了,就需要做冪等處理保證業務的一致性。
(2)分佈式環境中服務的超時重試機制,MQ 的自帶的重試功能或生產者重複生產消息導致接口被重複調用。如下是 MQ 導致接口多次調用的情況:
以上是常見的幾種導致接口被重複的調用的場景,清楚了接口爲什麼出現被重複調用,下面就來分析幾種解決這個問題的方案。
2、整理保證接口冪等性的方案
(1)前端控制方案
前端工程師在用戶提交了請求後讓提交按鈕變成禁用狀態或者跳轉到其他的頁面的方式,可以在一定程度上可以保證不會出現重複提交的問題。
此方案可以一定程度上防君子但是不防小人,因爲有的用戶直接拿到接口後刷接口,那麼此時就尷尬了。
(2)藉助 Redis 的 setnx 命令
用戶的請求 1 和請求 2 過來之後,我們在積分服務中拿到業務的唯一標識(如訂單的 id)到 Redis 中通過 setnx 命令並設定過期時間(過期時間可以根據業務來設定)來操作,判斷 setnx 命令是否可以操作成功,如果操作成功那麼允許執行業務,如果操作失敗就不處理業務。
(3)後端發放令牌的方式
用戶請求服務前,首先需要發送一個請求 1 到後端獲取令牌(如 JWT),獲取到令牌後請求 2 和請求 3(假設請求 3 是重複請求)都攜帶令牌請求到後端,後端接受到請求後先驗證令牌的有效性,如果令牌是無效的就直接返回;如果令牌是有效的就將令牌保存到數據庫中(數據庫中設置令牌是唯一鍵)
(a)保存令牌數據成功就開始處理對應的任務
(b)保存令牌信息失敗並且提示是唯一鍵衝突的異常,那麼手動捕獲異常並返回 “重複請求” 的提示給用戶。
上面採用的是將令牌存儲到數據庫的方式實現,也可以使用 Redis 來實現,如下圖所示:
生成令牌後緩存到 redis 中,然後請求攜帶令牌到後端,後端首先檢查令牌是否有效,如果無效就直接返回;如果令牌有效再去檢查令牌是否在 Redis 中,如果令牌不在 Redis 中就直接返回,如果在 Redis 中就刪除令牌然後處理業務。
(4)數據庫的唯一索引機制(去重表機制)
數據庫給請求中的唯一標識的參數做唯一索引(如訂單號),這樣請求過來之後首先去保存請求參數信息,如果保存成功就可以執行業務;如果保存失敗並提示唯一鍵衝突,直接返回提示用戶 “請勿重複” 提交,在高併發下,此方式的效率比較低。
(5)Redis 計數方式
每次請求到後端之後,首先通過 Redis 記錄一下請求中的唯一標識,然後使用 redis 計數並且返回計數的結果,如果計數的結果大於 1,則表示當前已請求是重複請求;如果計數等於 1 就可以處理業務。
(6)狀態機方式
有些業務可以是用狀態機的方案來實現,如請求 1 的目的是把訂單狀態從待支付 ----> 待發貨,請求 2 的目的是將把訂單狀態從待支付 ----> 交易關閉;如果請求 1 修改成功之後,請求 2 的操作是無效的。底層的 sql 如下:
update order set status = x where statue = '待支付';
通過返回的影響行數來判斷是否操作成功(影響行數爲 1 表示操作成功),如果操作失敗可以做對象的提示
總結:
(1)接口出現重複調用的情況有可能來源於前端(用戶多次點擊或者惡意攻擊等),也有可能後端(服務重試機制)
(2)保證接口冪等性的方案有多種,如去重表、Redis 計數或者 setnx 方式、攜帶令牌方式、狀態機方式等都可以實現。
(3)數據庫的樂觀鎖和悲觀鎖是可以解決併發問題,但是不一定可以保證冪等性問題(如請求 1 和請求 2 按照獲取鎖的先後順序執行,等於還是執行了兩次,可能還是會對業務造成影響)。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Yege3CDhDrYN5x9ydcnGcw