Java接口的幂等性
投資理財的案例,用戶可以充值、投資、提現,使用第三方支付進行充值,過程如下:
step1:用戶網站中輸入充值金額
step2:后端創建充值訂單入庫,此時訂單是待支付狀態
step3:跳轉到第三方支付頁面,輸入銀行卡,然后確認支付
step4:第三方支付通過我方提供的回調接口異步將充值結果告知我方
問題出在了step4,邏輯如下:
//返回通知處理結果,true:處理成功;false:處理失敗,第三方會繼續重試
public boolean rechargeNotice(第三方支付充值結果){
? ? try{
? ? ? ? //第三方充值結果中包含了我方的訂單id,從db中獲取充值訂單信息
? ? ? ? OrderModel order = this.getOrderById(訂單id); //@1
? ? ? ? //判斷訂單狀態是否是待支付狀態
? ? ? ? if(訂單狀態 == 待支付狀態){ //@2
? ? ? ? ? ? //將訂單狀態置為充值成功
? ? ? ? ? ? order.status(充值成功);
? ? ? ? ? ? orderService.update(order);
? ? ? ? ? ? //用戶賬戶可用余額增加
? ? ? ? ? ? this.accountService.incrBalance(用戶id,充值金額);
? ? ? ? ? ? return true;
? ? ? ? }else{
? ? ? ? ? ? //訂單已處理過,返回true
? ? ? ? ? ? return true;
? ? ? ? }
? ? }catch(Exception e){
? ? ? ? //記錄異常信息,返回通知失敗
? ? ? ? return false;
? ? }
}
并發情況時,上面邏輯是有問題的,同一筆訂單,同時進行2次通知,此時都會走到@1,此時看到order的狀態都是待支付狀態,然后都會進入@2,最后導致賬戶余額重復增加了,最后導致,充值1000,賬戶余額增加2000,這個問題,就是我們常說的冪等性的問題,是非常非常重要的一個技術點。
什么是冪等性?
對于同一筆業務操作,不管調用多少次,得到的結果都是一樣的。
冪等性設計
我們以對接支付寶充值為例,來分析支付回調接口如何設計?
如果我們系統中對接過支付寶充值功能的,我們需要給支付寶提供一個回調接口,支付寶回調信息中會攜帶(out_trade_no【商戶訂單號】,trade_no【支付寶交易號】),trade_no在支付寶中是唯一的,out_trade_no在商戶系統中是唯一的。
回調接口實現有以下實現方式。
方式1(普通方式)
過程如下:
1.接收到支付寶支付成功請求
2.根據trade_no查詢當前訂單是否處理過
3.如果訂單已處理直接返回,若未處理,繼續向下執行
4.開啟本地事務
5.本地系統給用戶加錢
6.將訂單狀態置為成功
7.提交本地事務
上面的過程,對于同一筆訂單,如果支付寶同時通知多次,會出現什么問題?當多次通知同時到達第2步時候,查詢訂單都是未處理的,會繼續向下執行,最終本地會給用戶加兩次錢。
此方式適用于單機其,通知按順序執行的情況,只能用于自己寫著玩玩。
方式2(jvm加鎖方式)
方式1中由于并發出現了問題,此時我們使用java中的Lock加鎖,來防止并發操作,過程如下:
1.接收到支付寶支付成功請求
2.調用java中的Lock加鎖
3.根據trade_no查詢當前訂單是否處理過
4.如果訂單已處理直接返回,若未處理,繼續向下執行
5.開啟本地事務
6.本地系統給用戶加錢
7.將訂單狀態置為成功
8.提交本地事務
9.釋放Lock鎖
分析問題:
Lock只能在一個jvm中起效,如果多個請求都被同一套系統處理,上面這種使用Lock的方式是沒有問題的,不過互聯網系統中,多數是采用集群方式部署系統,同一套代碼后面會部署多套,如果支付寶同時發來多個通知經過負載均衡轉發到不同的機器,上面的鎖就不起效了。此時對于多個請求相當于無鎖處理了,又會出現方式1中的結果。此時我們需要分布式鎖來做處理。
方式3(悲觀鎖方式)
使用數據庫中悲觀鎖實現。悲觀鎖類似于方式二中的Lock,只不過是依靠數據庫來實現的。數據中悲觀鎖使用for update來實現,過程如下:
1.接收到支付寶支付成功請求
2.打開本地事物
3.查詢訂單信息并加悲觀鎖
select * from t_order where order_id = trade_no for update;
4.判斷訂單是已處理
5.如果訂單已處理直接返回,若未處理,繼續向下執行
6.給本地系統給用戶加錢
7.將訂單狀態置為成功
8.提交本地事物
重點在于for update,對for update,做一下說明:
1.當線程A執行for update,數據會對當前記錄加鎖,其他線程執行到此行代碼的時候,會等待線程A釋放鎖之后,才可以獲取鎖,繼續后續操作。
2.事物提交時,for update獲取的鎖會自動釋放。
方式3可以正常實現我們需要的效果,能保證接口的冪等性,不過存在一些缺點:
1.如果業務處理比較耗時,并發情況下,后面線程會長期處于等待狀態,占用了很多線程,讓這些線程處于無效等待狀態,我們的web服務中的線程數量一般都是有限的,如果大量線程由于獲取for update鎖處于等待狀態,不利于系統并發操作。
方式4(樂觀鎖方式)
依靠數據庫中的樂觀鎖來實現。
1.接收到支付寶支付成功請求
2.查詢訂單信息
select * from t_order where order_id = trade_no;
3.判斷訂單是已處理
4.如果訂單已處理直接返回,若未處理,繼續向下執行
5.打開本地事物
6.給本地系統給用戶加錢
7.將訂單狀態置為成功,注意這塊是重點,偽代碼:
update t_order set status = 1 where order_id = trade_no where status = 0;
//上面的update操作會返回影響的行數num
if(num==1){
?//表示更新成功
?提交事務;
}else{
?//表示更新失敗
?回滾事務;
}
注意:
update t_order set status = 1 where order_id = trade_no where status = 0; 是依靠樂觀鎖來實現的,status=0作為條件去更新,類似于java中的cas操作;關于什么是cas操作,可以移步:什么是 CAS 機制 ( http://www.itsoku.com/article/63 )?
執行這條sql的時候,如果有多個線程同時到達這條代碼,數據內部會保證update同一條記錄會排隊執行,最終最有一條update會執行成功,其他未成功的,他們的num為0,然后根據num來進行提交或者回滾操作。
方式5(唯一約束方式)
依賴數據庫中唯一約束來實現。
我們可以創建一個表:
CREATE TABLE `t_uq_dipose` (
? `id` bigint(20) NOT NULL AUTO_INCREMENT,
? `ref_type` varchar(32) NOT NULL DEFAULT '' COMMENT '關聯對象類型',
? `ref_id` varchar(64) NOT NULL DEFAULT '' COMMENT '關聯對象id',
? PRIMARY KEY (`id`),
? UNIQUE KEY `uq_1` (`ref_type`,`ref_id`) COMMENT '保證業務唯一性'
);
對于任何一個業務,有一個業務類型(ref_type),業務有一個全局唯一的訂單號,業務來的時候,先查詢t_uq_dipose表中是否存在相關記錄,若不存在,繼續放行。
過程如下:
1.接收到支付寶支付成功請求
2.查詢t_uq_dipose(條件ref_id,ref_type),可以判斷訂單是否已處理
select * from t_uq_dipose where ref_type = '充值訂單' and ref_id = trade_no;
3.判斷訂單是已處理
4.如果訂單已處理直接返回,若未處理,繼續向下執行
5.打開本地事物
6.給本地系統給用戶加錢
7.將訂單狀態置為成功
8.向t_uq_dipose插入數據,插入成功,提交本地事務,插入失敗,回滾本地事務,偽代碼:
try{
? ? insert into t_uq_dipose (ref_type,ref_id) values ('充值訂單',trade_no);
? ? //提交本地事務:
}catch(Exception e){
? ? //回滾本地事務;
}
說明:
對于同一個業務,ref_type是一樣的,當并發時,插入數據只會有一條成功,其他的會違法唯一約束,進入catch邏輯,當前事務會被回滾,最終最有一個操作會成功,從而保證了冪等性操作。
關于這種方式可以寫成通用的方式,不過業務量大的情況下,t_uq_dipose插入數據會成為系統的瓶頸,需要考慮分表操作,解決性能問題。
上面的過程中向t_uq_dipose插入記錄,最好放在最后執行,原因:插入操作會鎖表,放在最后能讓鎖表的時間降到最低,提升系統的并發性。
關于消息服務中,消費者如何保證消息處理的冪等性?
每條消息都有一個唯一的消息id,類似于上面業務中的trade_no,使用上面的方式即可實現消息消費的冪等性。
總結
1.實現冪等性常見的方式有:悲觀鎖(for update)、樂觀鎖、唯一約束
2.幾種方式,按照最優排序:樂觀鎖 > 唯一約束 > 悲觀鎖
————————————————
版權聲明:本文為CSDN博主「RogerXue12345」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/rogerxue12345/article/details/107458839
總結
以上是生活随笔為你收集整理的Java接口的幂等性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 仙傲txt全集下载百度云(仙傲txt全集
- 下一篇: 深圳华侨城创意文化园核酸检测点(深圳华侨