首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文理解如何实现接口的幂等性

一文理解如何实现接口的幂等性

作者头像
全菜工程师小辉
发布2021-06-25 21:05:43
4.1K0
发布2021-06-25 21:05:43
举报

幂等,这个词来源自数学领域。幂等性衍生到软件工程中,它的语义是指:函数/接口可以使用相同的参数重复执行, 不应该影响系统状态,也不会对系统造成改变。

举一个简单的例子:正常设计的查询接口,不管调用多少次,都不会破坏当前的系统或数据,这就是一个幂等操作。

幂等的业务场景

在分布式系统中, 由于分布式天然特性的时序问题以及网络的不可靠性(机器、机架、机房故障、电缆被挖断等等), 重复请求很常见,接口幂等性设计就显得尤为重要。

幂等需要考虑的场景有很多,例如系统A是处理用户客户端发送过来的请求,无论是前端bug、脚本恶意发包、用户重复点击又或是网络超时导致的网络重发,都会造成系统A收到相同参数的网络请求。

对于处理消息队列请求的系统B和处理服务上游发送请求的系统C,也都存在网络超时导致的网络重发,所以要考虑接口的幂等性。

保障幂等性的原理

对于分布式系统来说,在JVM层面的锁已经失去作用,所以保证系统幂等性需要满足3个条件:

  1. 请求唯一标识:每一个请求必须有一个唯一标识。
  2. 处理唯一标识:每次处理完请求之后,必须有一个记录标识这个请求处理过了。
  3. 逻辑判断处理:每次接收请求需要进行判断之前是否处理过的逻辑处理。根据请求唯一标识查询是否存在处理唯一标识。

实际执行中要结合自身业务。

幂等性实现方案

1. token机制

针对客户端重复连续多次点击的情况,例如用户购物提交订单,提交订单的接口就可以通过token机制实现防止重复提交。

主要流程就是:

  1. 服务端提供生成请求token的接口。在存在幂等问题的业务执行前,向服务器获取请求token,服务器会把token保存到Redis中。
  2. 然后调用业务接口请求时,把请求token携带过去,一般放在请求头部。
  3. 服务器判断请求token是否存在redis中:存在则表示第一次请求,这时把Redis中的token删除,继续执行业务;如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执行。

这里要结合业务考虑这种场景:如果请求处理失败,前端是否需要重新申请token进行重试(因为此时token在服务端已经被删除)。

2. 数据库唯一索引

往数据库表里插入数据的时候,利用数据库的唯一索引特性,保证唯一的逻辑。唯一序列号可以是一个字段,例如订单的订单号,也可以是多字段的唯一性组合。

事务中包含多表数据的更新,业务要考虑处理事务回滚的问题。

3. Redis实现

Redis实现的方式就是将唯一序列号作为Key存入Redis,在请求处理之前,先查看Key是否存在。唯一序列号可以是一个字段,例如订单的订单号,也可以是多字段的唯一性组合。当然这里需要设置一个key的过期时间,否则Redis中会存在过多的key。具体校验流程如下图所示:

如果想要基于Redis实现幂等性防重框架,需要考虑如下两个问题:

  1. 如果第一次请求失败了,客户端重试,是否需要放行?
  2. 网络请求可能是get或者post(内部rpc协议除外),唯一序列号参数可能在url或是在body体里。则使用防重框架的新接口以及之前老业务接口能否做到版本兼容性?

建议业务使用方最好针对指定业务进行Redis的幂等方案。

Zookeeper同样也能实现上述功能,但由于Zookeeper是CP模型,性能不如Redis,另外针对防重场景,也并不需要Zookeeper高可靠性,所以优先推荐Redis。

4. ON DUPLICATE KEY UPDATE

有些业务场景是先根据索引从表中查询数据是否存在,如果存在则更新状态,不存在才插入数据。

这种情况下在并发量不大的时候没有问题,但是在高并发场景,可能会出现同时插入两条相同索引的情况,导致"Duplicate entry for key 'PRIMARY'"问题。

解决方法首先想到的当然是分布式锁。但分布式锁降低了吞吐量而且分布式锁依赖的组件,如Zookeeper或Redis如果出现网络超时,同样会影响在线服务。

所以一个简单的解决方法是使用mysql的INSERT INTO ...ON DUPLICATE KEY UPDATE语法,从而保证了接口的幂等性。

5. 状态机

对于很多业务,都存在业务流转状态的,每个状态都有前置状态以及最后的结束状态。

以订单为例,已支付的状态的前置状态只能是待支付,而取消状态的前置状态只能是待支付,通过这种状态机的流转就可以控制请求的幂等。假设当前状态是已支付,这时候如果支付接口又接收到了支付请求,则会抛异常或拒绝此次请求。


public enum OrderStatusEnum {
    UN_SUBMIT(0, 0, "待提交"),
    UN_PADING(0, 1, "待支付"),
    PAYED(1, 2, "已支付待发货"),
    DELIVERING(2, 3, "已发货"),
    COMPLETE(3, 4, "已完成"),
    CANCEL(0, 5, "已取消"),
    ;

    //前置状态
    private int preStatus;

    //状态值
    private int status;

    //状态描述
    private String desc;

    OrderStatusEnum(int preStatus, int status, String desc) {
        this.preStatus = preStatus;
        this.status = status;
        this.desc = desc;
    }
    //...
}

6. MVCC方案

这个方案严格上并不是解决幂等问题,更确切来说是解决并发问题。但高并发场景下,也是一种必须的保障措施。

多版本并发控制,该策略主要使用update with condition来保证多次外部请求调用对系统的影响是一致的。在系统设计的过程中,合理的使用乐观锁,通过version或者updateTime(timestamp)等其他条件,来做乐观锁的判断条件,这样保证更新操作即使在并发的情况下,也不会有太大的问题。例如

select * from tablename where condition=@condition //取出要更新的对象,带有版本versoin  
update tableName set name=#name#,version=version+1 where version=@version

在更新的过程中利用version来防止其他操作对对象的并发更新。如果直接拒绝是不理想的操作,则服务端需要一定的事务回滚与重试机制。

7. 分布式锁

有关分布式锁的讲解,可以查看博客《一文理解分布式锁的实现方式

分布式锁同样可以实现接口的幂等性,但由于分布式锁对系统负担来说相对要重一些,可以结合业务场景进行技术选型。

参考文档:

  1. https://zh.wikipedia.org/wiki/冪等
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 全菜工程师小辉 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 幂等的业务场景
  • 保障幂等性的原理
  • 幂等性实现方案
    • 1. token机制
      • 2. 数据库唯一索引
        • 3. Redis实现
          • 4. ON DUPLICATE KEY UPDATE
            • 5. 状态机
              • 6. MVCC方案
                • 7. 分布式锁
                相关产品与服务
                云数据库 Redis
                腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档