如何保證介面的冪等性?常見的實現方案有哪些?
冪等性問題是面試中常見的面試問題,也是分散式系統最常遇到的問題之一。在說冪等性之前,我們先來看一種情況,假如老王在某電商平臺進行購物,付款的時候不小心手抖了一下,連續點選了兩次支付,但此時伺服器沒做任何驗證,於是老王賬戶裡面的錢被扣了兩次,這顯然對當事人造成了一定的經濟損失,並且還會讓使用者喪失對平臺的信任。而冪等性問題說的就是如何防止介面的重複無效請求。
什麼是冪等性?如何保證介面的冪等性?
典型回答
冪等性最早是數學裡面的一個概念,後來被用於計算機領域,用於表示任意多次請求均與一次請求執行的結果相同,也就是說對於一個介面而言,無論呼叫了多少次,最終得到的結果都是一樣的。比如以下程式碼:
public class IdempotentExample {
// 變數
private static int count = 0;
/**
* 非冪等性方法
*/
public static void addCount() {
count++;
}
/**
* 冪等性方法
*/
public static void printCount() {
System.out.println(count);
}
}
對於變數 count 來說,如果重複呼叫 addCount() 方法的話,會一直累加 count 的值,因為 addCount() 方法就是非冪等性方法;而 printCount() 方法只是用來列印控制檯資訊的。因此,它無論呼叫多少次結果都是一樣的,所以它是冪等性方法。
知道了冪等性的概念,那如何保證冪等性呢?
冪等性的實現方案通常分為以下幾類:
●前端攔截
●使用資料庫實現冪等性
●使用 JVM 鎖實現冪等性
●使用分散式鎖實現冪等性
下面我們分別來看它們的具體實現過程。
1. 前端攔截
前端攔截是指通過 Web 站點的頁面進行請求攔截,比如在使用者點選完“提交”按鈕後,我們可以把按鈕設定為不可用或者隱藏狀態,避免使用者重複點選。
執行效果如下圖所示:
按鈕點選效果圖
核心的實現程式碼如下:
<script>
function subCli(){
// 按鈕設定為不可用
document.getElementById("btn_sub").disabled="disabled";
document.getElementById("dv1").innerText = "按鈕被點選了~";
}
</script>
<body style="margin-top: 100px;margin-left: 100px;">
<input id="btn_sub" type="button" value=" 提 交 " onclick="subCli()">
<div id="dv1" style="margin-top: 80px;"></div>
</body>
但前端攔截有一個致命的問題,如果是懂行的程式設計師或者黑客可以直接繞過頁面的 JS 執行,直接模擬請求後端的介面,這樣的話,我們前端的這些攔截就不能生效了。因此除了前端攔截一部分正常的誤操作之外,後端的驗證必不可少。
2. 資料庫實現
資料庫實現冪等性的方案有三個:
●通過悲觀鎖來實現冪等性
●通過唯一索引來實現冪等性
●通過樂觀鎖來實現冪等性
3. JVM 鎖實現
JVM 鎖實現是指通過 JVM 提供的內建鎖如 Lock 或者是 synchronized 來實現冪等性。使用 JVM 鎖來實現冪等性的一般流程為:首先通過 Lock 對程式碼段進行加鎖操作,然後再判斷此訂單是否已經被處理過,如果未處理則開啟事務執行訂單處理,處理完成之後提交事務並釋放鎖,執行流程如下圖所示:
JVM 鎖執行流程圖
JVM 鎖存在的最大問題在於,它只能應用於單機環境,因為 Lock 本身為單機鎖,所以它就不適應於分散式多機環境。
4. 分散式鎖實現
分散式鎖實現冪等性的邏輯是,在每次執行方法之前先判斷是否可以獲取到分散式鎖,如果可以,則表示為第一次執行方法,否則直接捨棄請求即可,執行流程如下圖所示:
分散式鎖執行流程圖
需要注意的是分散式鎖的 key 必須為業務的唯一標識,我們通常使用 Redis 或者 ZooKeeper 來實現分散式鎖;如果使用 Redis 的話,則用 set 命令來建立和獲取分散式鎖,執行示例如下:
127.0.0.1:6379> set lock true ex 30 nx
OK # 建立鎖成功
其中,ex 是用來設定超時時間的;而 nx 是 not exists 的意思,用來判斷鍵是否存在。如果返回的結果為“OK”,則表示建立鎖成功,否則表示重複請求,應該捨棄。
冪等性問題看似“高大上”其實說白了就是如何避免重複請求提交的問題,出於安全性的考慮,我們必須在前後端都進行冪等性驗證,同時冪等性問題在日常工作中又特別常見,解決的方案也有很多,但考慮到分散式系統情況,我們應該優先使用分散式鎖來實現。
●冪等性需要注意什麼問題?
●實現冪等性的關鍵步驟有哪些?
●說一說資料庫實現冪等性的執行細節?
知識擴充套件
1. 冪等性注意事項
冪等性的實現與判斷需要消耗一定的資源,因此不應該給每個介面都增加冪等性判斷,要根據實際的業務情況和操作型別來進行區分。例如,我們在進行查詢操作和刪除操作時就無須進行冪等性判斷。查詢操作查一次和查多次的結果都是一致的,因此我們無須進行冪等性判斷。刪除操作也是一樣,刪除一次和刪除多次都是把相關的資料進行刪除(這裡的刪除指的是條件刪除而不是刪除所有資料),因此也無須進行冪等性判斷。
2. 冪等性的關鍵步驟
實現冪等性的關鍵步驟分為以下三個:
●每個請求操作必須有唯一的 ID,而這個 ID 就是用來表示此業務是否被執行過的關鍵憑證,例如,訂單支付業務的請求,就要使用訂單的 ID 作為冪等性驗證的 Key;
●每次執行業務之前必須要先判斷此業務是否已經被處理過;
●第一次業務處理完成之後,要把此業務處理的狀態進行儲存,比如儲存到 Redis 中或者是資料庫中,這樣才能防止業務被重複處理。
3. 資料庫實現冪等性
使用資料庫實現冪等性的方法有三種:
●通過悲觀鎖來實現冪等性
●通過唯一索引來實現冪等性
●通過樂觀鎖來實現冪等性
接下來我們分別來看這些實現方式的具體執行過程。
① 悲觀鎖
使用悲觀鎖實現冪等性,一般是配合事務一起來實現,在沒有使用悲觀鎖時,我們通常的執行過程是這樣的,首先來判斷資料的狀態,執行 SQL 如下:
select status from table_name where id='xxx';
然後再進行新增操作:
insert into table_name (id) values ('xxx');
最後再進行狀態的修改:
update table_name set status='xxx';
但這種情況因為是非原子操作,所以在高併發環境下可能會造成一個業務被執行兩次的問題,當一個程式在執行中時,而另一個程式也開始狀態判斷的操作。因為第一個程式還未來得及更改狀態,所以第二個程式也能執行成功,這就導致一個業務被執行了兩次。
在這種情況下我們就可以使用悲觀鎖來避免問題的產生,實現 SQL 如下所示:
begin; # 1.開始事務
select * from table_name where id='xxx' for update; # 2.查詢狀態
insert into table_name (id) values ('xxx'); # 3.新增操作
update table_name set status='xxx'; # 4.更改操作
commit; # 5.提交事務
在實現的過程中需要注意以下兩個問題:
●如果使用的是 MySQL 資料庫,必須選用 innodb 儲存引擎,因為 innodb 支援事務;
●id 欄位一定要是主鍵或者是唯一索引,不然會鎖表,影響其他業務執行。
② 唯一索引
我們可以建立一個唯一索引的表來實現冪等性,在每次執行業務之前,先執行插入操作,因為唯一欄位就是業務的 ID,因此如果重複插入的話會觸發唯一約束而導致插入失敗。在這種情況下(插入失敗)我們就可以判定它為重複提交的請求。
唯一索引表的建立示例如下:
CREATE TABLE `table_name` (
`id` int NOT NULL AUTO_INCREMENT,
`orderid` varchar(32) NOT NULL DEFAULT '' COMMENT '唯一id',
PRIMARY KEY (`id`),
UNIQUE KEY `uq_orderid` (`orderid`) COMMENT '唯一約束'
) ENGINE=InnoDB;
③ 樂觀鎖
樂觀鎖是指在執行資料操作時(更改或新增)進行加鎖操作,其他時間不加鎖,因此相比於整個執行過程都加鎖的悲觀鎖來說,它的執行效率要高很多。
樂觀鎖可以通過版本號來實現,例如以下 SQL:
update table_name set version=version+1 where version=0;
小結
冪等性不但可以保證程式正常執行,還可以杜絕一些垃圾資料以及無效請求對系統資源的消耗。本課時我們講了冪等性的 6 種實現方式,包括前端攔截、資料庫悲觀鎖實現、資料唯一索引實現、資料庫樂觀鎖實現、JVM 鎖實現,以及分散式鎖的實現等方案,其中前端攔截無法防止懂行的人直接繞過前端進行模擬請求的操作。因此後端一定要實現冪等性處理,推薦的做法是使用分散式鎖來實現,這樣的解決方案更加通用。
相關文章
- 如何保證介面的冪等性?
- 高併發下如何保證介面的冪等性?
- SpringBoot如何保證介面的冪等性?六種方案一次講清楚~Spring Boot
- HTTP有哪些保證冪等性和安全性的方法? - mscharhagHTTP
- 騰訊二面:如何保證介面冪等性?高併發下的介面冪等性如何實現?
- 介面冪等性如何實現?
- 介面服務中的冪等性設計和防重保證,詳細分析冪等性的幾種實現方法
- MQ系列10:如何保證訊息冪等性消費MQ
- 關於如何在專案介面保證冪等性的一點思考
- 短影片app原始碼,實現冪等設計的常見方式APP原始碼
- 訊息佇列-如何保證訊息的不被重複消費(如何保證訊息消費的冪等性)佇列
- 面試官:volatile如何保證可見性的,具體如何實現?面試
- 什麼是冪等性?四種介面冪等性方案詳解!
- 介面冪等性解決方案
- Rabbit MQ 怎麼保證可靠性、冪等性、消費順序?MQ
- GitHub - fencyio:為RabbitMQ消費者提供冪等性保證的庫包GithubMQ
- 分散式系統中介面的冪等性分散式
- 短影片整套原始碼,如何實現冪等性校驗?原始碼
- 什麼是等保三級?等保三級的認證流程有哪些?
- [java]如何裂解RESTful的冪等性JavaREST
- 常見的ERP實施方法有哪些?
- 【等保】二級等保常見問題解答彙總
- 【過等保】2022年過等保常見問題解答
- 什麼是等保測評?解決方案有哪些?
- 高併發下的介面冪等性解決方案!
- Python的控制語句有哪些?常見內容介紹!Python
- Linux常見的開發命令有哪些?分類介紹!Linux
- 【Java面試】什麼是冪等?如何解決冪等性問題?Java面試
- 學習網路安全可以考證嗎?常見的證書有哪些?
- 答面試官問:怎麼實現介面冪等性面試
- 冪等性問題
- 等保、分保是什麼?等保與分保的區別有哪些?
- web常見的攻擊方式有哪些?如何防禦?Web
- 專案中對外暴露的http介面的安全性如何保證HTTP
- 分散式訊息佇列:如何保證訊息不被重複消費?(訊息佇列消費的冪等性)分散式佇列
- DDOS常見的型別有哪些?型別
- RabbitMQ 冪等性概念及業界主流解決方案MQ
- 一對一聊天平臺原始碼,實現冪等的8種方案原始碼