zoukankan      html  css  js  c++  java
  • 接口的幂等性

    如何保证接口的幂等性?

     

    今天我们来聊聊关于接口的幂等性问题。

    什么是幂等性

    所谓幂等,就是任意多次执行所产生的影响均与一次执行的影响相同。

    在 restful 规范中,常见的请求方式和接口幂等性关系如下:

    请求方式操作是否幂等
    GET 查询数据
    POST 新增数据
    PUT 更新数据 直接更新为某个值,满足幂等,如:set a = 1;累加操作的更新,不满足,如:set a = a+1
    DELETE 删除数据 根据唯一条件删除,满足幂等;否则,不满足,幂等,比如:根据某一条件删除一批数据后,又新增了一条满足该条件的数据,又执行了一次删除,那么就会删除掉新增的这条数据

    为什么会产生接口幂等性问题

    在计算机应用中,可能遇到网络抖动,临时故障,或者服务调用失败,尤其是分布式系统中,接口调用失败更为常见。为了保证服务的完整性,我们可能会发起接口的重试调用,如果接口不处理幂等,可能对系统造成很大的影响,因此接口的幂等设计尤其更为重要。

    对于业务中需要考虑幂等性的地方一般都是接口的重复请求,重复请求是指同一个请求因为某些原因被多次提交。导致这种情况的发生有以下几种常见的场景:

    1. 前端重复提交:用户在提交表单的时候,可能会因网络波动没有及时做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求。

    2. 接口超时重试:第三方调用接口时候,为了超时等异常情况造成的请求失败,都会添加重试机制,导致一个请求提交多次。

    3. 消息重复消费:当使用 MQ 消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费。

    幂等性解决方案

    那我们应该能怎样保证接口的幂等性呢?

    可以思考一下,第一种场景下,既然是用户重复提交导致的,那我们可以想办法让用户没办法重复提交。

    方案一:前端控制

    在前端做拦截,比如按钮点击一次之后就置灰或者隐藏。但是往往前端并不可靠,还是得后端处理才更放心。

    方案二:Token机制

    用户进入表单页面首先调用后台接口获取 token 并存入 redis,当用户提交表单时将 token 也作为入参,后端先删除 redis 中的 token,删除成功则保存表单数据,失败则提示用户重复提交。

    img

    这里为什么不先判断 redis 是否存在这个 token 再删除,是因为要保证操作的原子性,极端情况下,第一个请求查询到 redis 中存在这个 token,还没来得及删除,第二个请求进来,也查询到 redis 中存在这个 token,那么还是会造成重复提交的问题。

    token 机制需要先请求获取 token 的接口,在有些情况下很明显并不合适。我们大部分请求都是要落到数据库的,所以我们可以从数据库着手。

    方案三、唯一索引

    这种方案就比较好理解了,使用唯一索引可以避免脏数据的添加,当插入重复数据时数据库会抛异常,保证了数据的唯一性。唯一索引可以支持插入、更新、删除业务操作。

    方案四、悲观锁

    这里所说的悲观锁是基于数据库层面的,在获取数据时进行加锁,当同时有多个重复请求时,其他请求都无法进行操作。悲观锁只适用于更新操作。

    // 例如
    select name from t_goods where id=1 for update;
    

    注意:id 字段一定要是主键或者唯一索引,不然会锁住整张表,这是会死人的。悲观锁使用时一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用。

    在请求量比较大的情况下,使用悲观锁明显不合适,这时候就到乐观锁上场了。

    方案五、乐观锁

    可以通过版本号实现,为表增加一个 version 字段,当数据需要更新时,先去数据库里获取此时的version版本号。

    select version from t_goods where id=1
    

    更新数据时首先要对比版本号,如果不相等说明已经有其他的请求去更新数据了,提示更新失败。

    update t_goods set count=count+1,version=version+1 where version=#{version}
    

    还有一种是通过状态机实现的,其实也是乐观锁的原理。这种方法适合在有状态流转的情况下,比如订单的创建和付款,订单的创建肯定是在付款之前,这时我们可以通过在设计状态字段时,使用 int 类型,并且通过值类型的大小来实现幂等性。

    update t_goods set status=#{status} where id=1 and status<#{status}
    

    同样,乐观锁也只适用于更新操作。

    方案六、分布式锁

    有时候我们的业务不仅仅是操作数据库,也可能是发送短信、消息等等,那数据库层面的锁就不适合了。这种情况下就要考虑代码层面的锁了,而 java 的自带的锁在分布式集群部署的场景下并不适用,那么就可以采用分布式锁来实现(Redis 或 Zookeeper)。

    拿 Redis 分布式锁举例,比如一个订单发起支付请求,支付系统会去 Redis 缓存中查询是否存在该订单号的 Key,如果不存在,则以 Key 为订单号向 Redis 插入。查询订单是否已经支付,如果没有则进行支付,支付完成后删除该订单号的Key。通过 Redis 做到了分布式锁,只有这次订单支付请求完成,下次请求才能进来。当然这里需要设置一个Key 的过期时间,在发生异常的时候还要注意删除 Redis 的 Key。

    总结

    接口的幂等性是一个很常见的问题,需要根据具体业务场景的不同,选择合适的解决方案。

    END

    往期推荐

    你必须了解的分布式事务解决方案

    就这?分布式 ID 发号器实战

    略懂设计模式之工厂模式

    就这?Spring 事务失效场景及解决方案

    就这?一篇文章让你读懂 Spring 事务

    本文来自博客园,作者:靓仔聊编程,转载请注明原文链接:https://www.cnblogs.com/liangzaiit/p/15171618.html

    作者:Leo_wl
             
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    版权信息
  • 相关阅读:
    赋值问题
    构造方法的作用
    this的使用
    三目运算符和形参的使用
    构造方法作用:给所有对象进行相同的初始化操作
    成员变量和局部变量
    相关开发的书籍名汇集
    html ---- web sql 例子
    让input表单输入框不记录输入过信息的方法
    css实现两端对齐的3种方法
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/15497157.html
Copyright © 2011-2022 走看看