一、什么是幂等性?

一个操作,不论执行多少次,产生的效果和返回的结果都是一样的

二、应用场景

  1. 一个订单创建接口,第一次调用超时了,然后又调用了一次
  2. 订单创建后需要去执行扣减库存等等的后续操作,第一次调用超时后又调用一次
  3. 支付订单,已经发送过支付请求,第一次超时后又调用一次
  4. 订单完成支付后,发送消息处理一系列后续请求,中间件消息被多个机器(分布式)或者进程执行
  5. 对外提供接口的api如何保证幂等

这些问题都是在单体架构转成微服务架构之后,带来的问题。除了查询接口,增加、更新、删除都要保证幂等性,如何保证?

三、解决方方案

1、 全局唯一ID

根据每一次的操作和内容生成一个全局ID(guid等),在执行操作之前判断这个唯一ID是否存在,决定是否继续执行。

缺点:只是一种通用方案,不可能所有操作都做唯一性判读,一个是效率问题,另一个是要考虑分布式以及上下游的问题,实现起来较困难。
另外就是:如果是针对某张表的唯一索引,会出现重复更新的情况,所以更多的是要结合数据库字段以及状态机去实现,

业务情景:

  • 支付订单:生成唯一的支付凭证
  • 对外提供接口的api

2、 token机制

业务情景:

  • 防止订单重复提交,创建多个订单

业务要求:
页面数据只能被点击提交一次

发生原因:
重复点击、网络重发、nginx重发等原因

解决方法:
在数据提交之前,向服务端申请token,提交后后端校验token,并删除token

3、 数据库字段约束

业务情景:

  • 更新订单状态
  • 库存扣减

数据库字段约束主要用版本号控制,状态机约束,字段约束

字段约束主要是通过一些主键或者唯一索引去判断执行

插入:无法插入相同数据

更新:

  • 悲观锁:
    select * from table_xxx where id=’xxx’ for update;

版本号控制

更新:

  • 乐观锁:
    updateGoodsName(int id,String newName,int version);

    update goods set name=#{newName},version=#{version+1} where id=#{id} and version=${version}

状态机

设置固定的状态类型,只有同时满足该状态类型下的多个状态,才能执行

比如:

A:订单

待付款
待发货 contain 待发货
已发货 全部是已发货 已收货 退款 退货
已收货 全部已收货
退款中
退款成功
退款失败
退货中
退货成功
退货失败
已取消

B:订单(不存入库状态)

待付款
待发货(未确认、已确认)
已发货
已收货
退款中
退款成功
退款失败
退货中
退货成功
退货失败
已取消

参考链接
高并发的核心技术-幂等的实现方案
接口设计的幂等性考虑
分布式高并发系统如何保证对外接口的幂等性?
如何绘画状态机来描述业务的变化 - 人人都是产品经理