首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

幂等性及各种解决方案

什么是幂等性

幂等性简单的说就是相同条件下,一次请求和多次重复的请求接口的运行结果是相同的。

那什么情况下会出现幂等性问题呢?

前端重复提交表单:如用户在提交表单的时候,由于网络波动没有及时给用户做出提交成功响应,导致用户认为没有提交成功,一直点击提交按钮,此时就会发生表单重复提交。

接口超时重试:很多远程接口调用为了防止由于网络抖动导致的请求失败,都会引入重试机制,如feign的重试机制,导致一个请求发出多次。

消息重复消费:在使用消息中间件时,一个消息可能会被重复发送,此时就会发生重复消费。

天然具备幂等性的操作

以SQL为例,有些操作是天然具备幂等性的,它们无论执行多少次都不会改变状态。

查询语句

常量更新语句,如update table1 set col1 = 1 where col2 = 2。

删除语句,如delete from user where id=2等

借用别人的一张图:

其实判断是否具备幂等性的方法很简单,你就假设这条sql语句执行多次状态会不会改变,不会改变就具备幂等性。对于接口也是同样的方法,假设请求参数相同的情况下,执行多次,状态会不会改变。

对于主键自增的插入操作来说总是不具备幂等性的。

幂等性解决方案

数据库主键唯一性约束

利用数据库主键唯一性约束的特性,在插入数据的时候,如果主键已经存在,就会插入失败,从而保证幂等性。在这种方案下,唯一主键不应该是自增的,我们需要生成一个全局唯一主键ID。

数据库乐观锁实现幂等性

乐观锁方案, 一般适合更新操作,需要我们在数据库中多添加一个版本字段,在每次修改数据的时候,先进行版本号的比对,版本号比对成功才会进行更新操作,同时版本号也需进行修改。(相当于没修改一次数据,就升级一次版本)

如果你知道ABA的解决方案,你就很容易理解乐观锁实现幂等性。

乐观锁实现在sql语句上的体现:

token机制

常规token机制

以下订单为例

用户在提交订单前先向服务器申请一个token(比如在生成订单信息返回一个token),服务器将这个token保存在redis中。

用户提交订单时,携带该token过去

服务器判断token在redis中是否存在,存在表明是第一次请求,然后删除token,继续执行业务

如果不存在,则表明是重复操作,直接返回重复操作提示给客户端,这样就保证了业务代码不会被重复执行。

但是这里存在俩个问题

1、token的获取、比较和删除必须具备原子性

不具备原子性的伪代码

这种代码可能导致在高并发条件下,都get到了同样的数据,都判断成功,继续业务并发执行。

为了保证原子性,我们需要将这三个操作放在一个Lua脚本中运行,lua脚本如下:

具备原子性的伪代码就应该是:

当然我们也可能在删除token后执行业务代码失败,这样当用户重试携带的还是之前的token时,就会因为我们的防重设计导致不能执行业务代码。

这种情况下,我们可以在出现异常的时候,将token重新放回redis中,伪代码如下:

这就万无一失了吗?并没有,如果在执行catch代码块还没执行前,机器宕机了,那不就歇逼了?

所以说,业务调用失败后,用户应该重新获取token然后再请求。

非常规token机制

上面的token机制是网上常用的方法,我认为还可以用另一种方式来处理幂等性问题。流程是这样的:

同样用户需要先到服务器中申请一个token,不同的是,服务器没有将token存入redis中

用户请求的时候携带上token,服务器使用redis的setnx尝试将token保存到redis中

如果保存成功的话,说明是第一次请求,执行业务代码。如果保存失败,说明是重复请求,向用户提示重复请求。

伪代码就是酱紫的:

当然这种方案,当业务代码执行失败时,用户携带之前token重试,也会因为我们的防重设计不能执行业务代码。我们一样可以这样写:

为了防止出现宕机这样的情况,业务调用失败还是应该让用户重新获取token再发起请求。

上游服务传递全局唯一id实现幂等性

在调用远程接口时,传递一个唯一id过去,远程服务拿着这个全局id和自己的认证ID作为redis的键,去redis中查询,进行判断:

存在,说明之前处理过,直接返回重复请求的错误信息

不存在,将用全局id和自己的认证ID作为redis的键,保存在redis中,然后处理业务

注意要设置过期时间,否则可能会导致无限量存入redis中,导致redis不能正常使用

个人认为使用mysql来做幂等性,比较适合并发不是很高的场景,因为如果是高并发场景,mysql的压力本来就比较大,为了做幂等性,我们竟然让mysql承受更大的压力,明显不太合理,这种时候使用redis会更加合适。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20210302A04E9300?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券
http://www.vxiaotou.com