前言

大家好,我是路由器没有路

随着互联网的发展,Web API 已成为现代应用程序的重要组成部分,它答应不同的应用程序之间进行通讯和数据交换。

那么今日就来讲下关于 Web API 中接口幂等性的一些内容,期望对大家有所协助。

布景

在 Web API 中,接口幂等性是一种重要的概念,或许说是一种机制,它能够保证同一个恳求屡次履行时,不会对体系形成任何负面影响。

在 Web 应用程序中,因为网络延迟、恳求重试等要素,或许会导致同一个恳求被履行屡次。假如接口不具备幂等性,这样的重复恳求或许会导致体系状况的不一致性,数据的重复提交等问题。

因而,接口幂等性已成为 Web API 规划中的重要考虑要素。

接口幂等的运用场景

咱们在往常的事务开发中会常常接触到接口需求完结幂等的场景,但幂等往往又是开发最简略疏忽的一个点之一,因为幂等完结不当或缺少幂等完结导致严峻的体系故障,如:重复下单、重复发货、库存重复扣减等。

接口幂等性在许多场景下都是非常重要的。以下是一些常见的运用场景

  • 前端重复提交表单:用户填写完结表单提交,许多时分会因网络波动没有及时对用户做出提交成功呼应,致运用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单恳求。
  • 付出体系:在付出体系中,重复的付出恳求或许会导致用户的账户被重复扣款。因而,付出接口有必要具备幂等性,以保证同一个恳求屡次履行时,不会对用户的账户形成任何影响。
  • 订单体系:在订单体系中,重复的订单恳求或许会导致体系中呈现重复的订单。因而,订单接口有必要具备幂等性,以保证同一个恳求屡次履行时,不会对体系中的订单数据形成任何影响。
  • 数据库操作:在数据库操作中,重复的刺进、更新、删除恳求或许会导致数据的重复提交或丢失。因而,数据库接口有必要具备幂等性,以保证同一个恳求屡次履行时,不会对数据库中的数据形成任何影响。
  • 用户歹意刷单:例如在完结用户投票这种功用时,假如用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票成果与事实严峻不符。
  • 接口超时重复提交: 许多时分 HTTP 客户端工具(如 OpenFeign)都默许开启超时重试的机制,尤其是第三方调用接口时分,为了防止网络波动超时等形成的恳求失利,都会添加重试机制,导致一个恳求提交屡次。
  • 音讯进行重复消费:当运用 MQ 音讯中间件时分,假如发生音讯中间件呈现过错未及时提交消费信息,导致发生重复消费。

Restful API 接口的幂等性是怎样样的

Restful API 中,接口幂等性是一种非常重要的概念。Restful API 的幂等功用够经过 HTTP 办法来完结。

依据 HTTP 标准,GETHEAD 办法是幂等的,因为它们只是读取资源,不会对资源进行修正。

POSTPUTDELETE 办法则不是幂等的,因为它们会对资源进行修正。

办法 是否幂等 描绘
GET GET 办法用于获取资源,其一般不会对体系资源进行改变,所以是幂等的
HEAD Head 与 get 恳求类似,返回的呼应中没有详细内容,用于获取报头,所以也是幂等的
POST POST 办法一般用于创立新的资源。其每次履行都会新增数据,所以不是幂等的
PUT PUT 办法一般用于修正资源,所以也是非幂等的
DELETE DELETE 办法一般用户删除资源,所以也是非幂等的

幂等的不足之处

虽然接口幂等功用够保证同一个恳求屡次履行时,不会对体系形成任何负面影响,可是它也存在一些不足之处。

以下是一些常见的问题:

  • 添加了额定的操控幂等事务逻辑,杂乱了事务逻辑处理
  • 把本能够并行履行的操作变成了串行履行,降低了履行效率
  • 完结杂乱:完结接口幂等性或许需求运用一些杂乱的技能,如分布式锁、达观锁等,这或许会添加体系的杂乱度
  • 功用影响:完结接口幂等性或许会对体系的功用发生影响,如分布式锁或许会降低体系的吞吐量
  • 事务杂乱度:有些事务场景下,或许无法完结接口幂等性,如在某些事务场景下,重复的恳求或许会对体系发生影响,这时就需求在事务层面上进行处理

幂等完结的要害点

仔细分析幂等的界说,发现幂等在完结上关注的重点是辨别重复恳求和重复恳求对体系不会屡次形成不良影响。

那怎样判别相同恳求呢?

  1. 恳求方生成仅有恳求 Id,服务提供方经过恳求 Id 辨别恳求是否重复,假如恳求 Id 相同则断定为重复恳求;
  2. 服务提供方依据恳求参数经过一系列的 Hash 算法生成对应的 Hash 值,若 Hash 值相同则断定为重复恳求;

完结接口幂等性需求留意以下几个要害点:

  • 确认恳求的仅有性:需求确认每个恳求的仅有性,能够运用恳求头、恳求参数等方法来标识每个恳求的仅有性。
  • 防止重复履行:需求防止同一个恳求屡次履行的状况发生,能够运用分布式锁、达观锁等方法来完结。
  • 处理异常状况:需求处理异常状况,如网络异常、体系故障等状况,能够运用重试机制来处理异常状况。
  • 记载恳求日志:需求记载每个恳求的日志,以便于进行排查和追寻。

幂等完结的方法

完结接口幂等性的方法有许多,以下是一些常见的方法。

去重表

运用数据库的特性来完结幂等。一般是在表上构建一个仅有索引,那么只需某一个数据构建完毕,后边再次操作也无法成功写入。

常见的事务便是博客体系点赞功用,一个用户对一个博文点赞后,就把用户 id 与 博文 id 绑定,后续该用户点附和一个博文就无法刺进了。

或是在金融体系中,给用户创立金融账户,一个用户必定不能有多个账户,就在账户表中添加仅有索引来存储用户 id,这样即使重复操作用户也只能拥有一个账户。

状况标识

状况标识是很常见的幂等规划方法,首要思路便是经过状况标识的改变,保证事务中每个流程只会在对应的状况下履行,假如标识已经进入下一个状况,这时分来了上一个状况的操作就不答应改变状况,保证了事务的幂等性。

状况标识常常用在事务流程较长,修正数据较多的场景里。最经典的比方便是订单体系,假如一个订单要经历 创立订单 -> 订单付出/撤销-> 发货-> 确认收货->封闭订单 这几个过程。

那么就有或许一笔待付出的订单去付出,需求去账户里扣除对应的余额,耗费对应的优惠卷,可是因为付出完结后网络等原因返回了过错信息,这时分就会重试再次去进行账户余额扣减过程形成数据过错。

所以为了保证整个订单流程的幂等性,能够在订单信息中添加一个状况标识,一旦完结了一个过程就修正对应的状况标识。

比方订单付出成功后,就把订单标识为修正为付出完结待发货,现在再次调用订单付出或许撤销接口,会先判别订单状况标识,假如是已经付出过或许撤销订单,就不会再次付出了。

Token 机制(幂等标识)

Token 机制应该是适用范围最广泛的一种幂等规划方案了,详细完结方法也许多样化。可是中心思想便是每次操作都生成一个仅有 Token 凭据,服务器经过这个仅有凭据保证同样的操作不会被履行两次。

这个 Token 除了字面形式上的仅有字符串,也能够是多个标志的组合(比方上面提到的状况标志),甚至能够是时刻段标识等等。

换句话说,幂等标识能够在恳求头或呼应头中添加一个仅有的标识符,来标识每个恳求的仅有性。

举个比方,比方下单,这是一个典型的 Post 新增操作,要怎样防止用户屡次点击提交导致发生多个同样的订单呢。能够让用户提交的时分带一个仅有 Token,服务器只需判别该 Token 存在了就不答应提交(提示用户已重复下单),便能保证幂等性。

上面这个比方比较简略了解,可是事务比较简略。因为 Token 机制适用较广,所以其规划中要留意的要求也会依据事务不同而不同。

那么 Token 在何时生成,怎样生成?

这是该机制的中心,就拿下单服务来说,假如在用户提交订单的时分才生成 Token,那用户每次点提交都会生成新的 Token 然后都能提交成功,就不是幂等的了。有必要在用户提交内容之前,比方进入 checkout 页的时分生成 Token,用户在提交的时分内容带着 Token 一起提交,关于同一个页面不管用户提交多少次,就至多能成功一次。

所以 Token 生成的时机有必要保证能够使该操作具屡次履行都是相同的作用才行。运用 Token 机制就要求开发者对事务流程有较好的了解。

事例阐明

下面以“数据库达观锁 + 幂等性 + Go 伪代码”的事例来阐明。

在数据库中,达观锁是经过在数据表中添加一个版本号(或许时刻戳)字段来完结的。

在更新数据时,先查询当时数据的版本号,然后将要更新的数据的版本号设置为当时版本号+1,然后履行更新操作。假如更新成功,则阐明当时数据没有被其他线程修正,不然阐明当时数据已经被其他线程修正过,更新失利。

达观锁的完结能够很好地处理并发更新数据时的冲突问题,可是在某些状况下,或许会呈现幂等性问题。例如,在某个接口中,多个恳求一起对同一条数据进行更新,假如运用达观锁来完结幂等,那么或许会导致屡次更新操作,最终数据的成果或许并不是咱们期望的成果。

为了处理这个问题,咱们能够在接口层面添加一个幂等性校验,经过校验恳求的仅有标识符(如恳求 ID)来判别当时恳求是否已经处理过。假如当时恳求已经处理过,则直接返回成果,不然履行更新操作,并将恳求的仅有标识符记载到数据库中,以便下次校验。这样就能够保证同一个恳求只会被处理一次,然后完结幂等性。

下面是一个用 Golang 伪代码完结达观锁和幂等性的示例:

//界说数据结构
typeUserstruct{
IDint
Namestring
Versionint
}
//更新用户信息
funcupdateUser(db*sql.DB,user*User,requestIdstring)error{
//查询当时版本号
varcurrentVersionint
err:=db.QueryRow("SELECTversionFROMuserWHEREid=?",user.ID).Scan(&currentVersion)
iferr!=nil{
returnerr
}
//设置新版本号
user.Version=currentVersion+1
//履行更新操作
result,err:=db.Exec("UPDATEuserSETname=?,version=?WHEREid=?ANDversion=?",user.Name,user.Version,user.ID,currentVersion)
iferr!=nil{
returnerr
}
//判别更新是否成功
rowsAffected,err:=result.RowsAffected()
iferr!=nil{
returnerr
}
ifrowsAffected==0{
returnerrors.New("updatefailed")
}
//记载恳求ID,用于幂等性校验
_,err=db.Exec("INSERTINTOrequest_log(request_id)VALUES(?)",requestId)
iferr!=nil{
returnerr
}
returnnil
}
//幂等性校验
funccheckRequestId(db*sql.DB,requestIdstring)bool{
varcountint
err:=db.QueryRow("SELECTCOUNT(*)FROMrequest_logWHERErequest_id=?",requestId).Scan(&count)
iferr!=nil{
returnfalse
}
returncount>0
}
//处理恳求
funchandleRequest(db*sql.DB,user*User,requestIdstring)error{
//幂等性校验
ifcheckRequestId(db,requestId){
returnnil
}
//更新用户信息
err:=updateUser(db,user,requestId)
iferr!=nil{
returnerr
}
returnnil
}

在这个示例中,咱们界说了一个 User 结构体,其中包含了用户的 ID、姓名和版本号。

在更新用户信息时,咱们首先查询当时版本号,然后将要更新的数据的版本号设置为当时版本号+1,然后履行更新操作。假如更新成功,则阐明当时数据没有被其他线程修正,不然阐明当时数据已经被其他线程修正过,更新失利。

为了保证幂等性,咱们在 handleRequest()函数中添加了一个幂等性校验,经过查询恳求日志表来判别当时恳求是否已经处理过。

假如当时恳求已经处理过,则直接返回成果,不然履行更新操作,并将恳求的仅有标识符记载到数据库中,以便下次校验。这样就能够保证同一个恳求只会被处理一次,然后完结幂等性。

方案比较

方案 长处 缺陷
去重表 完结简略,易于了解和维护;能够防止重复提交和重复处理的问题 1)需求占用额定的存储空间;2) 只能用于刺进和删除操作;3)只能存在于仅有键场景
状况标识 完结简略,查询效率高 1)只适用于更新操作;2)表中需求添加额定的状况标识
Token 机制 完结相对杂乱,但安全性高,能够防止重放攻击和歹意恳求 1)需求生成仅有 Token;2)获取 Token 或许需求与服务提供方交互;3)需求凭借第三方存储(比方 Redis)

综上所述,选择哪种幂等完结方案取决于详细的事务需求和完结环境

假如对存储空间和查询效率要求较高,能够选择状况标识;假如对安全性要求较高,能够选择 Token 机制;假如对完结简略和易于维护要求较高,能够选择去重表。

总结

总之,接口幂等性是 Web API 规划中的重要考虑要素,能够保证同一个恳求屡次履行时,不会对体系形成任何负面影响。

完结接口幂等性需求留意恳求的仅有性、重复履行的处理、异常状况的处理以及恳求日志的记载等要害点。

一般能够运用去重表、状况标识、token 机制等方法来完结接口幂等性。