一、什么是幂等性
所谓幂等性,简单地说,就是对接口的多次调用所产生的结果和调用一次是一致的。扩展一下,这里的接口,可以理解为对外发布的HTTP接口或者Thrift接口,也可以是接收消息的内部接口,甚至是一个内部方法或操作。
二、应用场景
如上图所示用户下单后点击支付按钮进行支付。支付系统根据单号创建支付记录,然后调用银行接口,当一行扣款成功后,支付表修改支付状态为已支付。
但是,存在这样的情况,用户点击按钮无效,连续点击多次,是否会出现同一个订单支付多次?如何避免?这就是今天要讲的的幂等性。
作为对比,先看没有实现幂等性,也就是用户连续点击按钮,多次调用银行接口的情况。代码如下:
public void payForOrder(String orderId){
Order order = orderDao.findById(orderId);
Payment payment = paymentDao.findPaymentByOrderId(orderId);
payment.setOrderId(orderId);
payment.setMoney(order.getMoney());
payment.setPayStatus("0");//正在处理中
paymentDao.update(payment);
String flag = tranService.invoke(url,paymentId); //调用银行接口
payment.setPayStatus(flag);
paymentDao.update(payment);
}
有上面代码可知,当用户对一个单多次点击支付时,上面的代码一定会出现多次调用银行接口的情况。那么问题来了,如何保证幂等性?
介绍一个使用乐观锁实现幂等性的方案。通过新增版本号字段来实现。先看代码:
public payForOrder(String orderId){
Order order = orderDao.findById(orderId);
Payment payment = paymentDao.findPaymentByOrderId(orderId);
boolean invokeInterface = false ;
payment.setOrderId(orderId);
payment.setMoney(order.getMoney());
payment.setPayStatus("0");//正在处理中
payment.setVersion(0);
int records = paymentDao.updateByVersion(payment);
if(records ){
String flag = tranService.invoke(url,payment.getId()); //调用银行接口
payment.setPayStatus(flag);
paymentDao.update(payment);
}else{
logger.error("重复调用............+orderId="+orderId);
}
}
updateByVersion的模拟代码为:
update t_payment set orderId = #{orderId} , money=#{money}, payStatus=#{payStatus} version=#{ version } +1
where id=#{id} and version=#{version}
用户第一次点击时 paymentDao.updateByVersion(payment) 的返回值为1 ,此时可以调用银行接口;当第二次点击时updateByVersion的返回值为 0 ,不会调用银行接口,实现了幂等性。