深入理解接口幂等性
Posted 盛夏温暖流年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解接口幂等性相关的知识,希望对你有一定的参考价值。
一. 什么是接口幂等性
幂等性原本是数学上的概念,用在接口上就可以理解为:同一个接口,多次发出同一个请求,必须保证操作只执行一次。它是系统服务对外的一种承诺(注意不是一种实现),接口服务提供方承诺只要调用接口成功了,外部多次调用对系统的影响是一致的。
举一个最常见的例子,用户购买商品后支付扣款成功,但是此时网络发生了异常,导致返回结果失败。因为没收到返回结果,用户就会再次点击付款按钮,就会多付了一笔钱,一旦用户发现余额少了,开发人员就等着被祭天吧,这就是没有保证接口的幂等性。
需要强调一点是,声明为幂等的服务会认为调用方调用失败是常态,并且调用失败后必然会有重试。
现在我们已经知道了什么是接口幂等性,那又有哪些场景需要保证幂等性呢?
二. 需要保证幂等性的场景分析
以 SQL 为例,有下面三种场景,只有第三种场景需要开发人员使用策略来保证幂等性:
1. 场景一:查询
SELECT column1 FROM table1 WHERE column2 = 2
无论执行多少次都不会改变状态,是天然的幂等。
2. 场景二:常量赋值更新
UPDATE table1 SET column1 = 1 WHERE column2 = 2
无论执行成功多少次状态都是一致的,因此也是幂等操作。
3. 场景三:变量赋值更新
UPDATE table1 SET column1 = column1 + 1 WHERE column2 = 2
每次执行的结果都会发生变化,这种场景就不是幂等的。
三. 为什么需要实现幂等性
如果接口没有实现幂等性,遇到以下情况就会出现问题:
- 前端重复提交表单: 在填写表格时,用户填写完成提交,很多时候会因网络波动没有及时返回成功响应,致使用户认为没有提交成功,然后一直点提交按钮,这时就会导致重复提交表单请求。
- 用户恶意刷单: 在实现用户投票这种功能时,如果用户针对同一个人进行重复投票,会导致接口接收到用户重复提交的投票信息,会使投票结果与事实严重不符。
- 接口超时重复提交: 很多时候 HTTP 客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时,为了处理网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。
- 消息重复消费: 当使用 MQ 消息中间件时,如果消息中间件出现错误未及时提交消费信息,就会导致消息重复消费。
四. 引入幂等性后对系统的影响
幂等性是为了简化客户端逻辑处理,能防止重复提交,但却增加了服务端的逻辑复杂性和成本:
- 把并行执行的功能改为串行执行,降低了执行效率;
- 增加了额外控制幂等的业务逻辑,业务功能变得更加复杂;
所以在使用时,需要考虑引入幂等性的必要性,根据实际业务场景分析,除了业务上的特殊要求外,一般情况下不建议引入接口幂等性。
五. 如何实现接口幂等性
乐观锁
这里的乐观锁指的是用乐观锁的原理去实现,为数据字段增加一个 version 字段,当数据需要更新时,先去数据库里获取此时的 version 版本号:
SELECT version FROM tablename WHERE ...
更新数据时首先和版本号作对比,如果不相等说明已经有其他的请求去更新数据了,就会提示更新失败。
UPDATE tablename
SET count = count + 1,
version = version + 1
WHERE
version = #{version}
不过,乐观锁存在失效的情况,就是常说的 ABA 问题,不过如果 version 版本一直是自增的就不会出现这种情况,乐观锁主要用于读多写少的场景。
悲观锁
乐观锁可以实现的往往用悲观锁也能实现,是指在获取数据时进行加锁,当同时有多个重复请求时其他请求都无法进行操作。
SELECT
*
FROM
tablename
WHERE
id = 1 FOR UPDATE
注意:id 字段一定是主键或者唯一索引,不然会锁表。
悲观锁一般伴随事务一起使用,数据锁定时间可能会很长,根据实际情况选用;
数据库唯一性索引
利用数据库表单的特性来实现幂等,常用的一个思路是在表上构建唯一性索引,保证某一类数据一旦执行完毕,后续同样的请求再也无法成功写入。
以博客点赞为例,要想防止一个人重复点赞,可以设计一张去重表,将博客 id 与用户 id 绑定建立唯一索引,每当用户点赞时就往表中写入一条数据,这样重复点赞的数据就无法写入了。
分布式锁
去重表也可以使用分布式锁来代替,比如 Redis。
在分布式环境下,锁定全局唯一资源,使请求串行化,实际表现为互斥锁,防止重复,解决幂等。
Token 令牌
这种机制适用范围较广,有多种不同的实现方式。其核心思想是为每一次操作生成一个唯一性的凭证,也就是 Token。一个 Token 在操作的每一个阶段只有一次执行权,一旦执行成功就直接保存执行结果。
它主要针对客户端连续点击或者调用方的超时重试等情况,例如提交订单,此种操作就可以用 Token 的机制实现防止重复提交。
简单的说就是调用方在调用接口的时候先向后端请求一个全局 ID(Token),请求的时候携带这个全局 ID 一起请求(最好将其放到 Headers 中),后端需要对这个 Token 作为 Key,用户信息作为 Value 到 Redis 中进行键值内容校验,如果 Key 存在且 Value 匹配就执行删除命令,然后正常执行后面的业务逻辑。如果不存在对应的 Key 或 Value 不匹配就返回重复执行的错误信息,以此来保证幂等操作。
本篇博客参考了以下文章,感谢:
以上是关于深入理解接口幂等性的主要内容,如果未能解决你的问题,请参考以下文章