對于同一筆業(yè)務(wù)操作,不管調(diào)用多少次,得到的結(jié)果都是一樣的。
成都創(chuàng)新互聯(lián)堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:網(wǎng)站設(shè)計(jì)制作、網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的梅里斯網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
我們以對接支付寶充值為例,來分析支付回調(diào)接口如何設(shè)計(jì)?
如果我們系統(tǒng)中對接過支付寶充值功能的,我們需要給支付寶提供一個(gè)回調(diào)接口,支付寶回調(diào)信息中會(huì)攜帶(out_trade_no【商戶訂單號】,trade_no【支付寶交易號】),trade_no在支付寶中是唯一的,out_trade_no在商戶系統(tǒng)中是唯一的。
回調(diào)接口實(shí)現(xiàn)有以下實(shí)現(xiàn)方式。
過程如下:
1.接收到支付寶支付成功請求
2.根據(jù)trade_no查詢當(dāng)前訂單是否處理過
3.如果訂單已處理直接返回,若未處理,繼續(xù)向下執(zhí)行
4.開啟本地事務(wù)
5.本地系統(tǒng)給用戶加錢
6.將訂單狀態(tài)置為成功
7.提交本地事務(wù)
上面的過程,對于同一筆訂單,如果支付寶同時(shí)通知多次,會(huì)出現(xiàn)什么問題?當(dāng)多次通知同時(shí)到達(dá)第2步時(shí)候,查詢訂單都是未處理的,會(huì)繼續(xù)向下執(zhí)行,最終本地會(huì)給用戶加兩次錢。
此方式適用于單機(jī)其,通知按順序執(zhí)行的情況,只能用于自己寫著玩玩。
方式1中由于并發(fā)出現(xiàn)了問題,此時(shí)我們使用java中的Lock加鎖,來防止并發(fā)操作,過程如下:
1.接收到支付寶支付成功請求
2.調(diào)用java中的Lock加鎖
3.根據(jù)trade_no查詢當(dāng)前訂單是否處理過
4.如果訂單已處理直接返回,若未處理,繼續(xù)向下執(zhí)行
5.開啟本地事務(wù)
6.本地系統(tǒng)給用戶加錢
7.將訂單狀態(tài)置為成功
8.提交本地事務(wù)
9.釋放Lock鎖
分析問題:
Lock只能在一個(gè)jvm中起效,如果多個(gè)請求都被同一套系統(tǒng)處理,上面這種使用Lock的方式是沒有問題的,不過互聯(lián)網(wǎng)系統(tǒng)中,多數(shù)是采用集群方式部署系統(tǒng),同一套代碼后面會(huì)部署多套,如果支付寶同時(shí)發(fā)來多個(gè)通知經(jīng)過負(fù)載均衡轉(zhuǎn)發(fā)到不同的機(jī)器,上面的鎖就不起效了。此時(shí)對于多個(gè)請求相當(dāng)于無鎖處理了,又會(huì)出現(xiàn)方式1中的結(jié)果。此時(shí)我們需要分布式鎖來做處理。
使用數(shù)據(jù)庫中悲觀鎖實(shí)現(xiàn)。悲觀鎖類似于方式二中的Lock,只不過是依靠數(shù)據(jù)庫來實(shí)現(xiàn)的。數(shù)據(jù)中悲觀鎖使用for update來實(shí)現(xiàn),過程如下:
1.接收到支付寶支付成功請求
2.打開本地事物
3.查詢訂單信息并加悲觀鎖
select * from t_order where order_id = trade_no for update;
4.判斷訂單是已處理
5.如果訂單已處理直接返回,若未處理,繼續(xù)向下執(zhí)行
6.給本地系統(tǒng)給用戶加錢
7.將訂單狀態(tài)置為成功
8.提交本地事物
重點(diǎn)在于for update,對for update,做一下說明:
1.當(dāng)線程A執(zhí)行for update,數(shù)據(jù)會(huì)對當(dāng)前記錄加鎖,其他線程執(zhí)行到此行代碼的時(shí)候,會(huì)等待線程A釋放鎖之后,才可以獲取鎖,繼續(xù)后續(xù)操作。
2.事物提交時(shí),for update獲取的鎖會(huì)自動(dòng)釋放。
方式3可以正常實(shí)現(xiàn)我們需要的效果,能保證接口的冪等性,不過存在一些缺點(diǎn):
1.如果業(yè)務(wù)處理比較耗時(shí),并發(fā)情況下,后面線程會(huì)長期處于等待狀態(tài),占用了很多線程,讓這些線程處于無效等待狀態(tài),我們的web服務(wù)中的線程數(shù)量一般都是有限的,如果大量線程由于獲取for update鎖處于等待狀態(tài),不利于系統(tǒng)并發(fā)操作。
依靠數(shù)據(jù)庫中的樂觀鎖來實(shí)現(xiàn)。
1.接收到支付寶支付成功請求
2.查詢訂單信息
select * from t_order where order_id = trade_no;
3.判斷訂單是已處理
4.如果訂單已處理直接返回,若未處理,繼續(xù)向下執(zhí)行
5.打開本地事物
6.給本地系統(tǒng)給用戶加錢
7.將訂單狀態(tài)置為成功,注意這塊是重點(diǎn),偽代碼:
update t_order set status = 1 where order_id = trade_no where status = 0;
//上面的update操作會(huì)返回影響的行數(shù)num
if(num==1){
//表示更新成功
提交事務(wù);
}else{
//表示更新失敗
回滾事務(wù);
}
注意:
update t_order set status = 1 where order_id = trade_no where status = 0; 是依靠樂觀鎖來實(shí)現(xiàn)的,status=0作為條件去更新,類似于java中的cas操作;關(guān)于什么是cas操作,可以移步:什么是 CAS 機(jī)制?
執(zhí)行這條sql的時(shí)候,如果有多個(gè)線程同時(shí)到達(dá)這條代碼,數(shù)據(jù)內(nèi)部會(huì)保證update同一條記錄會(huì)排隊(duì)執(zhí)行,最終最有一條update會(huì)執(zhí)行成功,其他未成功的,他們的num為0,然后根據(jù)num來進(jìn)行提交或者回滾操作。
依賴數(shù)據(jù)庫中唯一約束來實(shí)現(xiàn)。
我們可以創(chuàng)建一個(gè)表:
CREATE TABLE `t_uq_dipose` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`ref_type` varchar(32) NOT NULL DEFAULT '' COMMENT '關(guān)聯(lián)對象類型',
`ref_id` varchar(64) NOT NULL DEFAULT '' COMMENT '關(guān)聯(lián)對象id',
PRIMARY KEY (`id`),
UNIQUE KEY `uq_1` (`ref_type`,`ref_id`) COMMENT '保證業(yè)務(wù)唯一性'
) ENGINE=InnoDB;
對于任何一個(gè)業(yè)務(wù),有一個(gè)業(yè)務(wù)類型(ref_type),業(yè)務(wù)有一個(gè)全局唯一的訂單號,業(yè)務(wù)來的時(shí)候,先查詢t_uq_dipose表中是否存在相關(guān)記錄,若不存在,繼續(xù)放行。
過程如下:
1.接收到支付寶支付成功請求
2.查詢t_uq_dipose(條件ref_id,ref_type),可以判斷訂單是否已處理
select * from t_uq_dipose where ref_type = '充值訂單' and ref_id = trade_no;
3.判斷訂單是已處理
4.如果訂單已處理直接返回,若未處理,繼續(xù)向下執(zhí)行
5.打開本地事物
6.給本地系統(tǒng)給用戶加錢
7.將訂單狀態(tài)置為成功
8.向t_uq_dipose插入數(shù)據(jù),插入成功,提交本地事務(wù),插入失敗,回滾本地事務(wù),偽代碼:
try{
insert into t_uq_dipose (ref_type,ref_id) values ('充值訂單',trade_no);
提交本地事務(wù):
}catch(Exception e){
回滾本地事務(wù);
}
說明:
對于同一個(gè)業(yè)務(wù),ref_type是一樣的,當(dāng)并發(fā)時(shí),插入數(shù)據(jù)只會(huì)有一條成功,其他的會(huì)違法唯一約束,進(jìn)入catch邏輯,當(dāng)前事務(wù)會(huì)被回滾,最終最有一個(gè)操作會(huì)成功,從而保證了冪等性操作。
關(guān)于這種方式可以寫成通用的方式,不過業(yè)務(wù)量大的情況下,t_uq_dipose插入數(shù)據(jù)會(huì)成為系統(tǒng)的瓶頸,需要考慮分表操作,解決性能問題。
上面的過程中向t_uq_dipose插入記錄,最好放在最后執(zhí)行,原因:插入操作會(huì)鎖表,放在最后能讓鎖表的時(shí)間降到最低,提升系統(tǒng)的并發(fā)性。
關(guān)于消息服務(wù)中,消費(fèi)者如何保證消息處理的冪等性?
每條消息都有一個(gè)唯一的消息id,類似于上面業(yè)務(wù)中的trade_no,使用上面的方式即可實(shí)現(xiàn)消息消費(fèi)的冪等性。
1.實(shí)現(xiàn)冪等性常見的方式有:悲觀鎖(for update)、樂觀鎖、唯一約束
2.幾種方式,按照最優(yōu)排序:樂觀鎖 > 唯一約束 > 悲觀鎖
3.希望大家能夠一起探討,一起成長,喜歡的關(guān)注一下(公眾號:javacode2018),可以留言
當(dāng)前題目:探討一下實(shí)現(xiàn)冪等性的幾種方式
轉(zhuǎn)載注明:http://m.rwnh.cn/article8/jcjcop.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站收錄、網(wǎng)站改版、企業(yè)建站、、動(dòng)態(tài)網(wǎng)站、網(wǎng)頁設(shè)計(jì)公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)