更新時(shí)間:2022-11-11 10:44:53 來源:動(dòng)力節(jié)點(diǎn) 瀏覽1592次
簡(jiǎn)單來說冪等保證了只要調(diào)用接口方法成功,外部多次調(diào)用對(duì)系統(tǒng)的影響是一致的,也就是一個(gè)請(qǐng)求多次重試的問題。
客戶端存在多次提交或者超時(shí)重試的情況;
分布式架構(gòu)中因網(wǎng)絡(luò)波動(dòng)采用重試機(jī)制,如Dubbo的重試機(jī)制;
消息推送重試,如MQ重試;
不冪等帶來的影響:比如在支付場(chǎng)景下,消費(fèi)者消費(fèi)扣款消息,對(duì)一筆訂單進(jìn)行扣款操作,該扣款操作需要扣除100元,在不冪等的情況下,如果消費(fèi)者多次發(fā)起請(qǐng)求,就會(huì)造成多次扣款。
冪等問題演示:
執(zhí)行如下SQL,初始化金額為100;
create table order_pay
(
id int default 0 not null
primary key,
amt decimal(9, 2) default 0.00 null comment '金額',
status int null comment '狀態(tài):0已完成/1處理中'
)comment '訂單支付表';
INSERT INTO test.order_pak (id, amt, status) VALUES (1, 100.00, 0);
pay接口:
@RestController
@RequestMapping("/test/order-pay")
public class OrderPayController {
@Autowired
IOrderPayService orderPayService;
@ApiOperation(httpMethod = "POST", value = "冪等性測(cè)試")
@PostMapping("pay")
public Resp pay(@RequestBody Req req) {
PayReq payReq = req.getData();
OrderPay order = orderPayService.getOne(new LambdaQueryWrapper().eq(OrderPay::getId, payReq.getBizId()));
order.setAmt(order.getAmt().subtract(payReq.getAmt()));
orderPayService.updateById(order);
return Resp.success("剩余金額更新為"+order.getAmt());
}
}
在不控制冪等的情況下,對(duì)pay接口連續(xù)發(fā)起5次請(qǐng)求,每一次扣減金額為10.00:
可以看到最后剩余金額更新為50 .00,我們發(fā)起一次請(qǐng)求,應(yīng)該只扣除10.00,當(dāng)遇到網(wǎng)絡(luò)重復(fù)或系統(tǒng)bug在不控制冪等的情況下會(huì)導(dǎo)致系統(tǒng)進(jìn)行了多次扣款。那如何進(jìn)行冪等控制呢?有什么方法呢?
保證冪等性的措施包括但不限于:
1.表單提交后按鈕置灰
限制客戶端請(qǐng)求
2.添加唯一索引
把唯一標(biāo)識(shí)作為唯一索引,在重復(fù)創(chuàng)建時(shí)會(huì)拋出唯一約束異常
3.全局唯一ID
針對(duì)業(yè)務(wù)操作和內(nèi)容生產(chǎn)全局唯一ID,在執(zhí)行操作時(shí)判斷ID是否存在來判斷是否已執(zhí)行
4.一鎖二查三更新
如果在流程處理過程中,業(yè)務(wù)要求不能并發(fā)執(zhí)行,可以在流程執(zhí)行之前根據(jù)業(yè)務(wù)ID獲取鎖,其他流程執(zhí)行時(shí)獲取鎖就會(huì)失敗,也就是同一時(shí)間該流程只能有一個(gè)能執(zhí)行成功,執(zhí)行完成后,釋放鎖,同時(shí)也需要在入口處增加業(yè)務(wù)狀態(tài)的判斷,以避免對(duì)請(qǐng)求的多次處理。這種方式不止可用于冪等的控制,也可以防止并發(fā)操作帶來的異常。
@ApiOperation(httpMethod = "POST", value = "冪等性測(cè)試")
@PostMapping("pay")
public Resp pay(@RequestBody Req req) throws Exception {
PayReq payReq = req.getData();
// 獲取分布式鎖
redisTools.lock(payReq.getBizId().toString());
OrderPay order = orderPayService.getOne(new LambdaQueryWrapper().eq(OrderPay::getId, payReq.getBizId()));
if (order.getStatus()==1){
order.setAmt(order.getAmt().subtract(payReq.getAmt()));
order.setStatus(0);
orderPayService.updateById(order);
}
// 釋防鎖
redisTools.unlock(payReq.getBizId().toString());
return Resp.success("剩余金額為"+order.getAmt());
}
以下是對(duì)pay接口進(jìn)行并發(fā)請(qǐng)求5次的結(jié)果:
可以看到,只有1個(gè)請(qǐng)求可以請(qǐng)求成功,另外4個(gè)請(qǐng)求在 redisTools.lock(payReq.getBizId().toString())獲取分布式鎖的步驟中拋出異常。
如果把分布式鎖的步驟去掉會(huì)發(fā)生什么樣的情況呢?
@ApiOperation(httpMethod = "POST", value = "冪等性測(cè)試")
@PostMapping("pay")
public Resp pay(@RequestBody Req req) throws Exception {
PayReq payReq = req.getData();
OrderPay order = orderPayService.getOne(new LambdaQueryWrapper().eq(OrderPay::getId, payReq.getBizId()));
if (order.getStatus()==1){
order.setAmt(order.getAmt().subtract(payReq.getAmt()));
order.setStatus(0);
orderPayService.updateById(order);
}
return Resp.success("剩余金額為"+order.getAmt());
}
依舊對(duì)pay接口進(jìn)行并發(fā)請(qǐng)求5次,查看日志:
5次請(qǐng)求都成功了,但是邏輯執(zhí)行了5遍,在復(fù)雜的業(yè)務(wù)流程下可能會(huì)引發(fā)其他不必要問題。
以上就是關(guān)于“Java冪等控制的實(shí)現(xiàn)”介紹,大家如果對(duì)此比較感興趣,想了解更多相關(guān)知識(shí),不妨來關(guān)注一下本站的Java視頻教程,里面的課程內(nèi)容從入門到精通,細(xì)致全面,通俗易懂,很適合小白學(xué)習(xí),希望對(duì)大家能夠有所幫助哦。
相關(guān)閱讀
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
有基礎(chǔ) 直達(dá)就業(yè)
業(yè)余時(shí)間 高薪轉(zhuǎn)行
工作1~3年,加薪神器
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問老師會(huì)電話與您溝通安排學(xué)習(xí)