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

什么是接口幂等性

接口幂等性,简单来讲就是指一个接口调用一次和调用N次的效果是同样的,不会产生其余的反作用。
注意:
这里的效果同样返回结果同样的区别,好比咱们都知道查询接口具备自然的幂等性,可是屡次调用查询接口的过程当中,若是有其余操做对查询的数据进行了新增、修改、删除操做,那么查询接口的返回结果就会不一直,可是这并不能说明该查询接口不具备幂等性。java

场景说明

典型场景,对指定订单发起一笔付款交易,不管交易接口调用一次仍是N次,都只能扣用户帐户一次钱。
通常最简单的幂等处理就是经过订单状态来进行控制,伪代码以下:web

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

代码实战

一、订单表说明微信

CREATE TABLE `tb_order` (
  `id` bigint(20NOT NULL AUTO_INCREMENT,
  `goods_code` varchar(255DEFAULT NULL COMMENT '商品编码',
  `goods_num` int(10DEFAULT NULL COMMENT '商品数量',
  `money` decimal(20,0DEFAULT NULL COMMENT '总金额',
  `status` tinyint(1DEFAULT NULL COMMENT '订单状态(0未支付,1支付)',
  `account` varchar(30CHARACTER 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'530000'laowan');

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

/**
 * @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 {
        //一、查询订单
        Order order = new Order();
        order.setId(Long.parseLong(orderId));
        order = orderMapper.selectByPrimaryKey(order);
        //为模拟并发,暂定100ms
        Thread.sleep(100);
        //二、根据订单状态判断,是否进行支付
        if(order.getStatus().equals(0)){
            log.info("进行支付,并更新订单状态");
            order.setStatus(1);
            orderMapper.updateByPrimaryKeySelective(order);
            return "支付完成";
        }else{
            log.info("已支付");
            return "已支付";
        }
    }
}

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

三、采用JMeter模拟并发支付
高并发

在这里插入图片描述


模拟每秒50个并发,执行结果为:

能够发现,在高并发的状况下,出现了大量重复支付的状况。

四、优化:经过for update添加悲观锁
4.一、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.二、OrderMapper.xml中添加selectForUpdate优化

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

4.三、修改支付订单方法ui

/**
     * 订单支付接口
     * @param orderId
     * @return
     * @throws InterruptedException
     */

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

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

4.四、再次使用JMeter压测


订单支付接口在高并发屡次调用的状况下,仍然只支付了一次,接口的幂等性获得保证。

总结:

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

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

更多精彩,关注我吧。

图注:跟着老万学java

本文分享自微信公众号 - 跟着老万学java(douzhe_2019)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索