028-86261949

当前位置:首页 > 技术交流 > springboot中的接口幂等性

springboot中的接口幂等性

2021/06/21 11:27 分类: 技术交流 浏览:0

1. 什么是接口幂等性

     幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

如某一元运算为幂等的时,其作用在任一元素两次后会和其作用一次的结果相同。例如,高斯符号便是幂等的。

     在计算机编程中,一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

幂等函数或幂等方法是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。

而什么又是接口的幂等性呢?

在HTTP/1.1中,对幂等性进行了定义。它描述了一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外),即第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用。直白一点的解释就是,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

2. 哪些情况可能需要保证接口幂等性

在软件系统中,一个请求在理想的情况下都能够正常的完成请求和响应的过程,并不会出现同一个请求出现多次去访问同一个资源的情况,但以下情况需要注意。

  • 前端重复提交表单

    用户在前端页面填写数据,在完成数据填写,提交数据的时候,由于网络波动,用户没有收到及时的响应结果,导致用户误以为未提交成功,而点击多次提交按钮,相同的表单数据提交多次。

  • 用户恶意刷单

    例如在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。

  • 消息重复消费

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

  • 其他框架接口重试机制

    在微服务项目中,很多HTTP 客户端工具或者框架都有超时重试的机制,尤其是第三方调用接口时候,为了防止网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。

因此为了避免对于一个操作发起多次请求的情况,因此需要保证被访问接口的幂等,防止多次请求造成资源出现异常的问题。

3. Restful API接口的幂等性

  Restful风格常见几种请求方式,幂等性探讨。

  • Get请求:天然幂等

相当于对于一个查询操作,不考虑其他因素的情况下,无论查询多少次,查询的结果始终一致。

Select   *  from   table

  • Post请求: 不满足幂等

Post请求一般用于新增操作,如果同一个数据被发送了多次,必然在数据库中新增多条数据,而实际期望的只需要一条。

如: insert into table (name,age) values (‘zs’,25);被执行多次

  • PUT请求:可能幂等 可能不幂等

Put请求一般用于修改数据,如果是修改某个字段值的情况下是幂等

如: update table set name = ‘ls’ where id = 1

此条sql语句无论执行多少次,结果都是一个  name最终都是ls

如果修改是某个字段值的增量  此时不是幂等

如:update table set age = age +2  where id = 1

此条sql语句执行多次,每次都会对age增加2

  • DELETE请求: 可能幂等  也可能不幂等

Delete请求一般用户删除  如果根据id删除  此时是幂等

如: delete from table  where id =1

如果是带条件的查询就不一定幂等

如:delete from table where id in(select * from table order by age limit 2)

此时执行多次  每次都会有新的两个年龄大的数据  因此 每次都会删除两条

4. 常见幂等性解决方案

   如何保证接口幂等性成为了微服务项目中经常需要解决的一个问题。产生这个问题的根本原因是由于原本只需要一次的操作,但因为网络各方面的原因,导致发送了多个同样操作的请求,导致数据不一致的情况,因此需要保证不论发送多少次请求,只能有一个操作成功,如果有一个成功,其他剩余的请求都不能成功,因此只需要保证唯一性即可。

方案一:数据库唯一主键

数据库唯一主键的实现主要是利用数据库中主键唯一约束的特性,一般来说唯一主键比较适用于“插入”时的幂等性,其能保证一张表中只能存在一条带该唯一主键的记录。

 

使用数据库唯一主键完成幂等性时需要注意的是,该主键一般来说并不是使用数据库中自增主键,而是使用分布式 ID 充当主键,这样才能能保证在分布式环境下 ID 的全局唯一性。

方案二:数据库乐观锁

数据库乐观锁方案一般只能适用于执行“更新操作”的过程,我们可以提前在对应的数据表中多添加一个字段,充当当前数据的版本标识。这样每次对该数据库该表的这条数据执行更新时,都会将该版本标识作为一个条件,值为上次待更新数据中的版本标识的值。

方案三:防重 Token 令牌

针对客户端连续点击或者调用方的超时重试等情况,例如提交订单,此种操作就可以用 Token 的机制实现防止重复提交。

 

简单的说就是调用方在调用接口的时候先向后端请求一个全局 ID(Token),请求的时候携带这个全局 ID 一起请求(Token 最好将其放到 Headers 中),后端需要对这个 Token 作为 Key,用户信息作为 Value 到 Redis 中进行键值内容校验,如果 Key 存在且 Value 匹配就执行删除命令,然后正常执行后面的业务逻辑。如果不存在对应的 Key 或 Value 不匹配就返回重复执行的错误信息,这样来保证幂等操作。

方案四下游传递唯一序列号

所谓请求序列号,其实就是每次向服务端请求时候附带一个短时间内唯一不重复的序列号,该序列号可以是一个有序 ID,也可以是一个订单号,一般由下游生成,在调用上游服务端接口时附加该序列号和用于认证的 ID。

 

当上游服务器收到请求信息后拿取该 序列号 和下游 认证ID 进行组合,形成用于操作 Redis 的 Key,然后到 Redis 中查询是否存在对应的 Key 的键值对,根据其结果:

 

如果存在,就说明已经对该下游的该序列号的请求进行了业务处理,这时可以直接响应重复请求的错误信息。

 

如果不存在,就以该 Key 作为 Redis 的键,以下游关键信息作为存储的值(例如下游商传递的一些业务逻辑信息),将该键值对存储到 Redis 中 ,然后再正常执行对应的业务逻辑即可。

#标签:幂等 后端 java