老万教你最简单接口幂等性控制

什么是接口幂等性

接口幂等性,简单来说就是指一个接口调用一次和调用N次的效果是一样的,不会产生其他的副作用。
注意:
这里的效果一样返回结果一样的区别,比如我们都知道查询接口具有天然的幂等性,但是多次调用查询接口的过程中,如果有其他操作对查询的数据进行了新增、修改、删除操作,那么查询接口的返回结果就会不一直,但是这并不能说明该查询接口不具有幂等性。

场景说明

典型场景,对指定订单发起一笔付款交易,无论交易接口调用一次还是N次,都只能扣用户账户一次钱。
一般最简单的幂等处理就是通过订单状态来进行控制,伪代码如下:

begin:
     根据订单号查询订单状态;
	 if(待支付){
	     扣款操作;
		 更新订单状态为已支付;
	 }
end;	

代码实战

1、订单表说明

CREATE TABLE `tb_order` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `goods_code` varchar(255) DEFAULT NULL COMMENT '商品编码',
  `goods_num` int(10) DEFAULT NULL COMMENT '商品数量',
  `money` decimal(20,0) DEFAULT NULL COMMENT '总金额',
  `status` tinyint(1) DEFAULT NULL COMMENT '订单状态(0未支付,1支付)',
  `account` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '账户',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

添加一条测试订单数据:订单id是1,状态0的未支付订单。

INSERT INTO `order`.`tb_order`(`id`, `goods_code`, `goods_num`, `money`, `status`, `account`) VALUES (1, 'wsj0001', 5, 3000, 0, 'laowan');

2、新建Order订单工程,实现订单支付接口

/**
 * @program: order
 * @description: 订单接口实现
 * @author: wanli
 * @create: 2020-09-21 16:11
 **/
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    @Autowired
    OrderMapper orderMapper;
    /**
     * 订单支付接口
     * @param orderId
     * @return
     * @throws InterruptedException
     */
    @Override
    public String payOrder(String orderId) throws InterruptedException {
        //1、查询订单
        Order order = new Order();
        order.setId(Long.parseLong(orderId));
        order = orderMapper.selectByPrimaryKey(order);
        //为模拟并发,暂定100ms
        Thread.sleep(100);
        //2、根据订单状态判断,是否进行支付
        if(order.getStatus().equals(0)){
            log.info("进行支付,并更新订单状态");
            order.setStatus(1);
            orderMapper.updateByPrimaryKeySelective(order);
            return "支付完成";
        }else{
            log.info("已支付");
            return "已支付";
        }
    }
}

说明:
这个应该是很多人的常见代码,先查询订单状态,判断订单状态是否是未支付状态,是则进行支付。

3、采用JMeter模拟并发支付
在这里插入图片描述
在这里插入图片描述
模拟每秒50个并发,执行结果为:
在这里插入图片描述
可以发现,在高并发的情况下,出现了大量重复支付的情况。

4、优化:通过for update添加悲观锁
4.1、OrderMapper.xml中添加selectForUpdate

  <select id="selectForUpdate" resultType="com.laowan.order.model.Order">
        select  * from tb_order t where t.id = #{orderId} for update
  </select>

4.2、OrderMapper.xml中添加selectForUpdate

public interface OrderMapper extends BaseMapper<Order> {
    Order selectForUpdate(Long orderId);
}

4.3、修改支付订单方法

/**
     * 订单支付接口
     * @param orderId
     * @return
     * @throws InterruptedException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public String payOrder(String orderId) throws InterruptedException {
        //1、查询订单
        Order order = new Order();
        order.setId(Long.parseLong(orderId));
        //通过
        order = orderMapper.selectForUpdate(Long.parseLong(orderId));
        //为模拟并发,暂定100ms
        Thread.sleep(100);
        //2、根据订单状态判断,是否进行支付
        if(order.getStatus().equals(0)){
            log.info("进行支付,并更新订单状态");
            order.setStatus(1);
            orderMapper.updateByPrimaryKeySelective(order);
            return "支付完成";
        }else{
            log.info("已支付");
            return "已支付";
        }
    }

说明:
for update一定要配合事务使用,不然执行完查询语句后,会自动释放锁。
给方法添加 @Transactional事务注解后,只有方法执行完毕才会自动释放锁。

4.4、再次使用JMeter压测
在这里插入图片描述
订单支付接口在高并发多次调用的情况下,仍然只支付了一次,接口的幂等性得到保证。

总结:

1、不能简单的通过订单状态来控制接口的幂等性,高并发情况下,多个线程同时查到未支付状态的订单,就容易出现重复支付的情况。
2、通过for update对订单记录加上排他锁,使的该订单记录不能被其他线程查询到,也不能进行其他修改操作。只有当事务提交,释放锁后,其他操作该订单的接口才能执行。
3、一定要给订单号加上索引,避免锁表。(Innodb引擎中,查询和更新操作如果不走索引,就会进行全表锁定。本例中由于订单号是主键,所以不用加)。
4、@Transactional注解不但能控制一个方法中的多个数据修改操作的原子性,也能控制锁的释放。比如本例中,尽管只有一个修改操作,但是一定要添加@Transactional注解让方法执行完后自动释放锁。

当然实现接口幂等性还有很多其他的方法,这里只是说明一个最典型的错误场景,并通过简单的添加悲观锁,实现接口的幂等性控制。

更多精彩,关注我吧。
图注:跟着老万学java

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

斗者_2013

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值