天天看點

我是這樣給同僚分析幂等性問題的

引子

在日常一些技術設計方案評審會上,經常會提到注意服務接口的幂等性問題,最近有個同學就跑到跟前問我,到底啥是幂等性?

今天就關于服務幂等性的一系列問題,在此将材料稍作整理,分享給大家~

尤其在目前分布式/微服務化的今天,提供的後端服務接口,注意做好幂等性設計很有必要。

本文大綱

1、何為幂等性?

2、幂等性主要場景有哪些?

3、幂等性的作用是什麼?

4、如何解決幂等性問題?

幂等(idempotence),來源于數學中的一個概念,例如:幂等函數/幂等方法(指用相同的參數重複執行,并能獲得相同結果的函數,這些函數不影響系統狀态,也不用擔心重複執行會對系統造成改變)。

簡單了解即:多次調用對系統的産生的影響是一樣的,即對資源的作用是一樣的。

我是這樣給同僚分析幂等性問題的

幂等性

幂等性強調的是外界通過接口對系統内部的影響, 隻要一次或多次調用對某一個資源應該具有同樣的副作用就行。

注意:這裡指對資源造成的副作用必須是一樣的,但是傳回值允許不同!

根據上面對幂等性的定義我們得知:産生重複資料或資料不一緻,這個絕大部分是由于發生了重複請求。

這裡的重複請求是指同一個請求在一些情況下被多次發起。

導緻這個情況會有哪些場景呢?

  • 微服務架構下,不同微服務間會有大量的基于 http,rpc 或者 mq 消息的網絡通信,會有第三個情況【未知】,也就是逾時。如果逾時了,微服務架構會進行重試。
  • 使用者互動的時候多次點選,無意地觸發多筆交易。
  • MQ 消息中間件,消息重複消費
  • 第三方平台的接口(如:支付成功回調接口),因為異常也會導緻多次異步回調
  • 其他中間件/應用服務根據自身的特性,也有可能進行重試。

幂等性主要保證多次調用對資源的影響是一緻的。

在闡述作用之前,我們利用資源處理應用來說明一下:

HTTP 與資料庫的 CRUD 操作對應: 

   PUT :CREATE

   GET :READ 

   POST :UPDATE

   DELETE :DELETE

(其實不光是資料庫,任何資料如檔案圖表都是這樣)

1)查詢

SELECT * FROM users WHERE xxx;      

複制代碼

不會對資料産生任何變化,天然具備幂等性。

2)新增

INSERT INTO users (user_id, name) VALUES (1, 'zhangsan');      

case1:帶有唯一索引(如:`user_id`),重複插入會導緻後續執行失敗,具有幂等性;

case2:不帶有唯一索引,多次插入會導緻資料重複,不具有幂等性。

3)修改

case1:直接指派,不管執行多少次 score 都一樣,具備幂等性。

UPDATE users SET score = 30 WHERE user_id = 1;      

case2:計算指派,每次操作 score 資料都不一樣,不具備幂等性。

UPDATE users SET score = score + 30 WHERE user_id = 1;      

4)删除

case1:絕對值删除,重複多次結果一樣,具備幂等性。

DELETE FROM users WHERE id = 1;      

case2:相對值删除,重複多次結果不一緻,不具備幂等性。

DELETE top(3) FROM users;      

總結:通常隻需要對寫請求(新增 &更新)作幂等性保證。 

我們在網上搜尋幂等性問題的解決方案,會有各種各樣的解法,但是如何判斷哪種解決方案對于自己的業務場景是最優解,這種情況下,就需要我們抓問題本質。

經過以上分析,我們得到了解決幂等性問題就是要控制對資源的寫操作。

我們從問題各個環節流程來分析解決:

我是這樣給同僚分析幂等性問題的

幂等性問題分析

4.1 控制重複請求

控制動作觸發源頭,即前端做幂等性控制實作

相對不太可靠,沒有從根本上解決問題,僅算作輔助解決方案。

主要解決方案:

  • 控制操作次數,例如:送出按鈕僅可操作一次(送出動作後按鈕置灰)
  • 及時重定向,例如:下單/支付成功後跳轉到成功提示頁面,這樣消除了浏覽器前進或後退造成的重複送出問題。

4.2 過濾重複動作

控制過濾重複動作,是指在動作流轉過程中控制有效請求數量。

1)分布式鎖

利用 Redis 記錄目前處理的業務辨別,當檢測到沒有此任務在進行中,就進入處理,否則判為重複請求,可做過濾處理。

訂單發起支付請求,支付系統會去 Redis 緩存中查詢是否存在該訂單号的 Key,如果不存在,則向 Redis 增加 Key 為訂單号。查詢訂單支付已經支付,如果沒有則進行支付,支付完成後删除該訂單号的 Key。通過 Redis 做到了分布式鎖,隻有這次訂單訂單支付請求完成,下次請求才能進來。

分布式鎖相比去重表,将放并發做到了緩存中,較為高效。思路相同,同一時間隻能完成一次支付請求。

2)token 令牌

應用流程如下:

1)服務端提供了發送 token 的接口。執行業務前先去擷取 token,同時服務端會把 token 儲存到 redis 中;

2)然後業務端發起業務請求時,把 token 一起攜帶過去,一般放在請求頭部;

3)伺服器判斷 token 是否存在 redis 中,存在即第一次請求,可繼續執行業務,執行業務完成後将 token 從 redis 中删除;

4)如果判斷 token 不存在 redis 中,就表示是重複操作,直接傳回重複标記給 client,這樣就保證了業務代碼不被重複執行。

我是這樣給同僚分析幂等性問題的

token令牌處理流程圖

3)緩沖隊列

把所有請求都快速地接下來,對接入緩沖管道。後續使用異步任務處理管道中的資料,過濾掉重複的請求資料。

優點:同步轉異步,實作高吞吐。

缺點:不能及時傳回處理結果,需要後續監聽處理結果的異步傳回資料。

我是這樣給同僚分析幂等性問題的

緩沖隊列

4.3 解決重複寫

實作幂等性常見的方式有:悲觀鎖(for update)、樂觀鎖、唯一限制。

1)悲觀鎖(Pessimistic Lock)

簡單了解就是:假設每一次拿資料,都有認為會被修改,是以給資料庫的行或表上鎖。

當資料庫執行 select for update 時會擷取被 select 中的資料行的行鎖,是以其他并發執行的 select for update 如果試圖選中同一行則會發生排斥(需要等待行鎖被釋放),是以達到鎖的效果。

select for update 擷取的行鎖會在目前事務結束時自動釋放,是以必須在事務中使用。(注意 for update 要用在索引上,不然會鎖表)
START TRANSACTION; # 開啟事務SELETE * FROM users WHERE id=1 FOR UPDATE;UPDATE users SET name= 'xiaoming' WHERE id = 1;COMMIT; # 送出事務      

2)樂觀鎖(Optimistic Lock)

簡單了解就是:就是很樂觀,每次去拿資料的時候都認為别人不會修改。更新時如果 version 變化了,更新不會成功。

不過,樂觀鎖存在失效的情況,就是常說的 ABA 問題,不過如果 version 版本一直是自增的就不會出現 ABA 的情況。
UPDATE users SET name='xiaoxiao', version=(version+1) WHERE id=1 AND version=version;      

缺點:就是在操作業務前,需要先查詢出目前的 version 版本

另外,還存在一種:狀态機控制

例如:支付狀态流轉流程:待支付->支付中->已支付

具有一定要的前置要求的,嚴格來講,也屬于樂觀鎖的一種。

3)唯一限制

常見的就是利用資料庫唯一索引或者全局業務唯一辨別(如:source+序列号等)。

這個機制是利用了資料庫的主鍵唯一限制的特性,解決了在 insert 場景時幂等問題。但主鍵的要求不是自增的主鍵,這樣就需要業務生成全局唯一的主鍵。

全局 ID 生成方案:

  • UUID:結合機器的網卡、當地時間、一個随記數來生成 UUID;
  • 資料庫自增 ID:使用資料庫的 id 自增政策,如 MySQL 的 auto_increment。
  • Redis 實作:通過提供像 INCR 和 INCRBY 這樣的自增原子指令,保證生成的 ID 肯定是唯一有序的。
  • 雪花算法-Snowflake:由 Twitter 開源的分布式 ID 生成算法,以劃分命名空間的方式将 64-bit 位分割成多個部分,每個部分代表不同的含義。

小結:按照應用上的最優收益,推薦排序為:樂觀鎖 > 唯一限制 > 悲觀鎖。

後記

聽了我以上大段的講述後,他好像收獲感滿滿的似的說:了解了...

但是出于自身責任感,我還得叮囑他幾句:

1)幂等性處理 雖然複雜了業務處理,也可能會降低接口的執行效率,但是為了保證系統資料的準确性,是非常有必要的;

2)遇到問題,善于發現并挖掘本質問題,這樣解決起來才能高效且精準;

3)選擇自身業務場景适合的解決方案,而不要去硬套一些現成的技術實作,無論是組合還是創新,要記住适合的才是最好的。

- END -

作者:架構精進之路,十年研發風雨路,大廠架構師,CSDN 部落格專家,專注架構技術沉澱學習及分享,職業與認知更新,堅持分享接地氣兒的幹貨文章,期待與你一起成長。

關注并私信我回複“01”,送你一份程式員成長進階大禮包,歡迎勾搭。

Thanks for reading!