天天看點

幂等設計1.概述2.解決方案

1.概述

接口幂等性是指使用者對于同一操作發起的一次請求或者多次請求的結果是一緻的,不會因為多次點選而産生了副作用。

幂等有兩個次元:一是空間次元上的幂等,即幂等對象的範圍,是個人還是機構,是某一次交易還是某種類型的交易...二是時間次元上的幂等,即幂等的保證時間,是幾秒、幾分鐘還是永久性的...

2.解決方案

1. select + insert

這是最常見的處理方式,我們在儲存資料的時候為了防止重複資料的産生,一般會在insert之前,根據名稱或者其他查詢資料庫,看資料是否存在。如果已經存在我們執行update操作,如果不存在執行insert操作。适合并發不高的平台。

幂等設計1.概述2.解決方案

2. 加悲觀鎖(for update)

在轉賬或者是商品購買場景中,但存在多個使用者對同一款庫存有限的産品同一時間進行下單操作。如果是采用先去查詢庫存數量情況,再進行庫存數量改變,可能會出現商品超賣的情況。

幂等設計1.概述2.解決方案

經常我們遇到這種情況通常情況下通過如下sql鎖住單行資料:

select * from item where id=1 for update;

使用悲觀鎖具體流程如下:

幂等設計1.概述2.解決方案

需要特别注意的是:如果使用的是mysql資料庫,存儲引擎必須用innodb,因為它才支援事務。此外,這裡id字段一定要是主鍵或者唯一索引,不然會鎖住整張表。

悲觀鎖采用的是「先擷取鎖再通路」的政策,來保障資料的安全。但是加鎖政策,依賴資料庫實作,會增加資料庫的負擔,且會增加死鎖的發生幾率。此外,對于不會發生變化的隻讀資料,加鎖隻會增加額外不必要的負擔。在實際的實踐中,對于并發很高的場景并不會使用悲觀鎖,因為當一個事務鎖住了資料,那麼其他事務都會發生阻塞,會導緻大量的事務發生積壓拖垮整個系統。

3. 加樂觀鎖

樂觀鎖和悲觀鎖相比,樂觀鎖一般假設認為資料一般情況下不會造成沖突,是以在資料進行送出更新的時候,才會正式對資料的沖突與否進行檢測。

1.通過加版本号version

使用資料版本(Version)記錄機制實作,這是樂觀鎖最常用的一種實作方式。何謂資料版本?即為資料增加一個版本辨別,一般是通過為資料庫表增加一個數字類型的 “version” 字段來實作。當讀取資料時,将version字段的值一同讀出,資料每更新一次,對此version值加1。當我們送出更新的時候,判斷資料庫表對應記錄的目前版本資訊與第一次取出來的version值進行比對,如果資料庫表目前版本号與第一次取出來的version值相等,則予以更新,否則認為是不允許更新。

https://blog.csdn.net/sunwenhao_2017/article/details/81565783
幂等設計1.概述2.解決方案

2.通過加其他條件

樂觀鎖定的第二種實作方式和第一種差不多,同樣是在需要樂觀鎖控制的表中增加一個字段,名稱無所謂,字段類型使用精确的時間戳, 和上面的version類似,也是在更新送出的時候檢查目前資料庫中資料的時間戳和自己更新前取到的時間戳進行對比,如果一緻則OK,否則就是版本沖突。

4. 加唯一索引

絕大數情況下,為了防止重複資料的産生,我們都會在表中加唯一索引,這是一個非常簡單,并且有效的方案。

alter table `order` add UNIQUE KEY `un_code` (`code`);

加了唯一索引之後,第一次請求資料可以插入成功。但後面的相同請求,插入資料時會報Duplicate entry '002' for key 'order.un_code異常,表示唯一索引有沖突。

具體流程圖如下:

幂等設計1.概述2.解決方案

5. token令牌

該方案跟之前的所有方案都有點不一樣,需要兩次請求才能完成一次業務操作。

  1. 第一次請求擷取token
  2. 第二次請求帶着這個token,完成業務操作。

第一步,先擷取token,将其存入redis,并設計好過期時間

第二步,做具體業務操作。

幂等設計1.概述2.解決方案

6. 加分布式鎖

其實前面介紹過的加唯一索引或者加防重表,本質是使用了資料庫的分布式鎖,也屬于分布式鎖的一種。但由于資料庫分布式鎖的性能不太好,我們可以改用:redis或zookeeper。

目前主要有三種方式實作redis的分布式鎖:

  1. setNx指令2.set指令3.Redission架構
幂等設計1.概述2.解決方案

7. 狀态機控制

很多時候業務表是有狀态的,比如訂單表中有:1-下單、2-已支付、3-完成、4-撤銷等狀态。如果這些狀态的值是有規律的,按照業務節點正好是從小到大,我們就能通過它來保證接口的幂等性。

假如id=123的訂單狀态是已支付,現在要變成完成狀态。

update item_order set status=3 where id=1 and status=2;

第一次請求時,該訂單的狀态是已支付,值是2,是以該update語句可以正常更新資料,sql執行結果的影響行數是1,訂單狀态變成了3。

後面有相同的請求過來,再執行相同的sql時,由于訂單狀态變成了3,再用status=2作為條件,無法查詢出需要更新的資料,是以最終sql執行結果的影響行數是0,即不會真正的更新資料。但為了保證接口幂等性,影響行數是0時,接口也可以直接傳回成功。

幂等設計1.概述2.解決方案