數據庫鎖、分佈式鎖實現接口冪等性
在我們的實際開發中,會由於接口超時後重試機制、MQ 的重複消費等場景都會帶來接口冪等性的問題,下面我們介紹幾種通過鎖的方式實現接口冪等性的方案。
1、數據庫唯一主鍵
數據庫唯一主鍵的實現原理是使用數據庫中主鍵唯一約束的特性,一般來說唯一主鍵比較適用於添加數據時的冪等性場景,唯一主鍵能保證一張表中只能存在一條該唯一主鍵的記錄,方案的實現的流程如下圖所示:
使用數據庫唯一主鍵實現接口冪等性時需要注意的是,爲了保證在分佈式環境下 ID 的全局唯一性,這裏的主鍵一般使用分佈式 ID 充當主鍵,常見的幾種生成分佈式 ID 的方案的整理如下:
2、數據庫悲觀鎖
假設我們查詢到某個訂單 A 後,然後更新訂單的狀態,此時就可以使用數據庫的悲觀鎖實現。使用悲觀鎖可以添加 for update 關鍵字,即對這行記錄上鎖(上行鎖還是表鎖取決於 where 後面的查詢字段索引的類型,假設訂單的 id 是唯一主鍵,那麼就是行鎖),如下圖所示:
使用的查詢語句如下所示:
select * from order where order_id = 123 for update
若是線程 T1 執行了 select … for update,此時就會對這行記錄上鎖了,那麼直到整個線程 T1 提交事務之前,T2 線程來查詢相同的訂單 id 數據會進入到阻塞狀態。當 T1 完成業務處理之後釋放鎖,其他的線程纔可以繼續操作這條數據。
悲觀鎖在同一事務操作過程中鎖住了一行數據,別的請求過來只能等待,如果當前事務耗時比較長,就很影響接口性能。對於 Mysql 數據庫,要求存儲引擎必須用 innodb,因爲 innodb 才支持事務,另外 orderId 字段一定要是主鍵或者唯一索引,不然會鎖整張表。
3、數據庫樂觀鎖
數據庫樂觀鎖方案一般適用於執行 "更新操作" 的過程,我們可以提前在對應的數據表中額外添加一個字段來充當數據的版本標識。這樣每次對該數據表的這條數據執行更新時,都會將該版本標識作爲一個條件,值爲上次待更新數據中的版本標識的值,如下圖所示的:
每次更新的時候,我們使用版本號來進行字段校驗以及進行 update 更新,sql 如下所示:
UPDATE order SET status=2,version=version+1 WHERE id=123
AND version=0;
在 WHERE 後面的條件 id=123 AND version=0 被執行後,id=123 的 version 被更新爲 1,所以如果重複執行該條 sql 語句將不生效,因爲 id=123 AND version=0 的數據已經不存在,通過這樣方式就能保住更新的冪等,多次更新對結果不會產生影響。
4、狀態機機制
在業務表中有狀態的時候,如訂單表中的訂單狀態(待提交、待支付、待發貨、待收貨等等)這些狀態的值是有規律的,按照業務節點一級級的流轉,那麼我們就能利用這個特性實現接口的冪等,如下如所示的節點狀態流轉過程:
假如訂單 id 爲 123 的訂單當前狀態是待支付,當用戶支付後,訂單的狀態要變成待發貨,更新的語句如下:
update order set status = 2 where order_id = 123 and status = 1
線程 T1 請求時,該訂單的狀態是待支付(status = 1),所以該 update 語句可以正常更新數據,sql 執行結果的影響行數是 1,訂單狀態變成了 2。
線程 T2 相同的請求過來,再執行相同的 sql 時,由於訂單狀態變成了 2,再用 status=1 作爲條件,無法查詢出需要更新的數據,所以最終 sql 執行結果的影響行數是 0,即不會真正的更新數據。數據表記錄中的狀態來作爲條件做更新其實跟樂觀鎖使用版本號類似。
5、分佈式鎖
唯一主鍵與樂觀鎖的本質是使用了數據庫的鎖,但由於數據庫鎖的性能不太好,所以我們可使用 Redis、Zookeeper 等中間件來實現分佈式鎖的功能,以 Redis 爲例實現冪等的流程圖:
當用戶通過瀏覽器發起請求,服務端接收到請求後,使用唯一標識(如訂單號)作爲 Redis 的 key。然後使用 Redis 的 set 命令,將該唯一標識存儲到 Redis 中並設置超時時間,業務根據 Redis 返回的結果做不同的處理:
(1)如果保存 Redis 成功,那麼允許當前的線程執行業務操作。
(2)若保存 Redis 失敗,說明是重複請求,則直接響應客戶端,請求處理成功。
在分佈式鎖中要設置一個合理的過期時間,如果設置過短,無法有效的防止重複請求;如果設置過長,佔用了 Redis 的存儲空間,在請求非常多的情況下會給 Redis 造成壓力。
總結:
每種冪等性實現方案都有各自的優缺點,我們需要根據實際的業務場景選擇更合適的方案來解決冪等性問題。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/sR4zUZsv408JDczvAv6_gA