专注Java教育14年 全国咨询/投诉热线:444-1124-454
赢咖4LOGO图
始于2009,口口相传的Java黄埔军校
首页 学习攻略 Java学习 Java幂等控制的实现

Java幂等控制的实现

更新时间:2022-11-11 10:44:53 来源:赢咖4 浏览715次

简单来说幂等保证了只要调用接口方法成功,外部多次调用对系统的影响是一致的,也就是一个请求多次重试的问题。

需要考虑幂等的场景:

客户端存在多次提交或者超时重试的情况;

分布式架构中因网络波动采用重试机制,如Dubbo的重试机制;

消息推送重试,如MQ重试;

不幂等带来的影响:比如在支付场景下,消费者消费扣款消息,对一笔订单进行扣款操作,该扣款操作需要扣除100元,在不幂等的情况下,如果消费者多次发起请求,就会造成多次扣款。

幂等性问题解决方案

幂等问题演示:

执行如下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 '状态: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 = "幂等性测试")
@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());
}
}

在不控制幂等的情况下,对pay接口连续发起5次请求,每一次扣减金额为10.00:

可以看到最后剩余金额更新为50 .00,我们发起一次请求,应该只扣除10.00,当遇到网络重复或系统bug在不控制幂等的情况下会导致系统进行了多次扣款。那如何进行幂等控制呢?有什么方法呢?

幂等性的实现

保证幂等性的措施包括但不限于:

1.表单提交后按钮置灰

限制客户端请求

2.添加唯一索引

把唯一标识作为唯一索引,在重复创建时会抛出唯一约束异常

3.全局唯一ID

针对业务操作和内容生产全局唯一ID,在执行操作时判断ID是否存在来判断是否已执行

4.一锁二查三更新

如果在流程处理过程中,业务要求不能并发执行,可以在流程执行之前根据业务ID获取锁,其他流程执行时获取锁就会失败,也就是同一时间该流程只能有一个能执行成功,执行完成后,释放锁,同时也需要在入口处增加业务状态的判断,以避免对请求的多次处理。这种方式不止可用于幂等的控制,也可以防止并发操作带来的异常。

@ApiOperation(httpMethod = "POST", value = "幂等性测试")
@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());
}

以下是对pay接口进行并发请求5次的结果:

可以看到,只有1个请求可以请求成功,另外4个请求在 redisTools.lock(payReq.getBizId().toString())获取分布式锁的步骤中抛出异常。

不加锁与加锁的区别

如果把分布式锁的步骤去掉会发生什么样的情况呢?

@ApiOperation(httpMethod = "POST", value = "幂等性测试")
@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());
}

依旧对pay接口进行并发请求5次,查看日志:

5次请求都成功了,但是逻辑执行了5遍,在复杂的业务流程下可能会引发其他不必要问题。

以上就是关于“Java幂等控制的实现”介绍,大家如果对此比较感兴趣,想了解更多相关知识,不妨来关注一下本站的Java视频教程,里面的课程内容从入门到精通,细致全面,通俗易懂,很适合小白学习,希望对大家能够有所帮助哦。

提交申请后,顾问老师会电话与您沟通安排学习

免费课程推荐 >>
技术文档推荐 >>