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

标签: | 发表时间:2021-06-17 14:56 | 作者:
出处:https://mp.weixin.qq.com

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

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

幂等的业务场景

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

幂等需要考虑的场景有很多,例如系统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. 状态机

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

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

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

//前置状态
privateintpreStatus;

//状态值
privateintstatus;

//状态描述
privateString desc;

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

6. MVCC方案

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

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

      select*fromtablenamewherecondition=@condition //取出要更新的对象,带有版本versoin        
updatetableNamesetname=#name#,version=version+1whereversion=@version

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

7. 分布式锁

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

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

参考文档:

  1. https://zh.wikipedia.org/wiki/冪等


相关 [理解 接口 幂等] 推荐:

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

- -
幂等性衍生到软件工程中,它的语义是指:函数/接口可以使用相同的参数重复执行, 不应该影响系统状态,也不会对系统造成改变. 举一个简单的例子:正常设计的查询接口,不管调用多少次,都不会破坏当前的系统或数据,这就是一个幂等操作. 在分布式系统中, 由于分布式天然特性的时序问题以及网络的不可靠性(机器、机架、机房故障、电缆被挖断等等), 重复请求很常见,接口幂等性设计就显得尤为重要.

高并发下如何保证接口的幂等性?

- -
接口幂等性问题,对于开发人员来说,是一个跟语言无关的公共问题. 本文分享了一些解决这类问题非常实用的办法,绝大部分内容我在项目中实践过的,给有需要的小伙伴一个参考. 不知道你有没有遇到过这些场景:. form表单时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样. 接口超时问题,通常会引入了.

电商课题:幂等性

- - 博客园_旁观者
幂等性的数学表达:f(f(x)) = f(x). 幂等性是系统接口对外的一种承诺. 幂等性指的是,使用相同参数对同一资源重复调用某个接口的结果与调用一次的结果相同. 幂等性的一个实现是,使你的接口必须返回 0(成功),即使这时资源或动作已经停止并且无工作要完成. 防范 POST 重复提交. HTTP POST 操作既不是安全的,也不是幂等的(至少在HTTP规范里没有保证).

HTTP幂等性概念和应用

- rockmaple - 酷壳 - CoolShell.cn
[ 感谢 Todd 同学投递本文 ]. 基于HTTP协议的Web API是时下最为流行的一种分布式服务提供方式. 无论是在大型互联网应用还是企业级架构中,我们都见到了越来越多的SOA或RESTful的Web API. 为什么Web API如此流行呢. 我认为很大程度上应归功于简单有效的HTTP协议.

实战!聊聊幂等设计

- - 掘金 架构
大家好,我是捡田螺的小男孩. 公众号: 捡田螺的小男孩. 幂等是一个数学与计算机科学概念. 在数学中,幂等用函数表达式就是: f(x) = f(f(x)). 比如求绝对值的函数,就是幂等的, abs(x) = abs(abs(x)). 计算机科学中,幂等表示一次和多次请求某一个资源应该具有同样的副作用,或者说,多次请求所产生的影响与一次请求执行的影响效果相同.

详解服务幂等性设计

- - 掘金 架构
本文正在参加 「金石计划. hello,大家好,我是张张,「架构精进之路」公号作者. 在日常工作中的一些技术设计方案评审会上,经常会有人提到注意服务接口的幂等性问题,最近就有个组内同学就跑到跟前问我,幂等性到底是个啥. 在目前分布式/微服务化的今天,提供的服务能力丰富多样,基于 HTTP 协议的 Web API 是时下最为流行的一种分布式服务提供方式,对于服务的幂等性保障尤为重要.

创建订单实现幂等的一点思考

- - 文章 – 伯乐在线
大部分文章都会说,同一个操作,进行多次操作后,结果是一样的,就可以说这个操作是支持幂等的. 感觉不太准确,比如一个http get操作,可能每次的结果都不一样,但是其实是幂等的. 看了很多文章,感觉下面的定义比较准确:. 一个操作如果多次任意执行所产生的影响(或者叫副作用),都是相同的. 如果一个用户分两次下单,购买的商品都是一样的.

分布式幂等问题解决方案三部曲

- - SegmentFault 最新的文章
文章目的:本文旨在提炼一套分布式幂等问题的思考框架,而非解决某个具体的分布式幂等问题. 在这个框架体系内,会有一些方案举例说明. 文章目标:希望读者能通过这套思考框架设计出符合自己业务的完备的幂等解决方案. (1)背景介绍,为什么会有幂等. (2)什么是幂等,这个定义非常重要,决定了整个思考框架. (3)解决幂等问题的三部曲,也是作者的思考框架.

Kafka笔记—可靠性、幂等性和事务 - luozhiyun - 博客园

- -
这几天很忙,但是我现在给我的要求是一周至少要出一篇文章,所以先拿这篇笔记来做开胃菜,源码分析估计明后两天应该能写一篇. Kafka只对“已提交”的消息(committed message)做有限度的持久化保证. 当Kafka的若干个Broker成功地接收到一条消息并写入到日志文件后,它们会告诉生产者程序这条消息已成功提交.

消息幂等(去重)通用解决方案,RocketMQ

- - 薛定谔的风口猪
消息中间件是分布式系统常用的组件,无论是异步化、解耦、削峰等都有广泛的应用价值. 我们通常会认为,消息中间件是一个可靠的组件——这里所谓的可靠是指,只要我把消息成功投递到了消息中间件,消息就不会丢失,即消息肯定会至少保证消息能被消费者成功消费一次,这是消息中间件最基本的特性之一,也就是我们常说的“AT LEAST ONCE”,即消息至少会被“成功消费一遍”.