接口幂等性

一. 什么是幂等性?

在数学里,幂等有两种主要的定义。

在某二元运算下,幂等元素是指被自己重复运算的结果等于它自己的元素。例如,乘法下唯一两个幂等实数为0和1。

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

在计算机领域,幂等性指多次操作对系统产生的影响与一次操作相同。举个例子,假设要删除用户A,无论请求多少次,操作结果都是删除用户A,而不会删除用户B。
在RESTful风格的接口中,幂等性表现在HTTP请求方法中:

GET:幂等,即要获取用户A的信息,多次请求系统,返回的皆是用户A的信息。是返回结果相同而不是返回内容相同。

POST: 非幂等,用户注册,多次调用接口,会新增多条用户数据。

PUT: 幂等,put请求与post的区别是,post请求倾向于新增数据,而put请求倾向于更新数据,如果数据不存在则会根据客户端提供的完整数据资源创建数据。所以对于put操作来说,多次调用接口产生的结果是一样的,即客户端提交的数据都会被更新到系统中。

PATCH: 非幂等,patch是对put的补充。顾名思义patch即补丁,用于更新子资源的部分内容,同样地,如果要更新的数据不存在则允许创建数据。可以发现patch和put非常相似,那为什么put是幂等的,而patch非幂等呢?因为patch允许根据客户端提供的某个值动态计算更新内容,例如每次调用某个参数+1,则多次调用会产生不同结果。

DELETE: 删除一次和多次删除都是把数据删除。(注意可能返回结果不一样,删除的数据不存在,返回0,删除的数据多条,返回结果多个,在不考虑返回结果的情况下,删除操作也是具有幂等性的)

幂等: GET, PUT, DELETE

非幂等: POST, PATCH

二. 什么是接口幂等性?

接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。

举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...,这就没有保证接口的幂等性.

三. 如何保证幂等

1. token机制

token机制的流程:

1. 服务端提供了发送token的接口。我们在分析业务的时候,哪些业务是存在幂等问题的,就必须在执行业务前,先去获取token,服务器会把token保存到redis中。
2. 然后调用业务接口请求时,把token携带过去,一般放在请求头部。
3. 服务器判断token是否存在redis中,存在表示第一次请求,然后删除token,继续执行业务。
4. 如果判断token不存在redis中,就表示是重复操作,直接返回重复标记给client,这样就保证了业务代码,不被重复执行。

关键点 先删除token,还是后删除token:

后删除token:如果进行业务处理成功后,删除redis中的token失败了,这样就导致了有可能会发生重复请求,因为token没有被删除。这个问题其实是数据库和缓存redis数据不一致问题.

先删除token:如果系统出现问题导致业务处理出现异常,业务处理没有成功,接口调用方也没有获取到明确的结果,然后进行重试,但token已经删除掉了,服务端判断token不存在,认为是重复请求,就直接返回了,无法进行业务处理了。

先删除token可以保证不会因为重复请求,业务数据出现问题。出现业务异常,可以让调用方配合处理一下,重新获取新的token,再次由业务调用方发起重试请求就ok了

token机制缺点:

业务请求每次请求,都会有额外的请求(一次获取token请求、判断token是否存在的业务)。
其实真实的生产环境中,1万请求也许只会存在10个左右的请求会发生重试,为了这10个请求,我们让9990个请求都发生了额外的请求。

总结:

'''
# 流程: 
第一次请求客户端获取token服务端保存token到reids
第二次请求判断token在不在redis中.
	在表示第一次请求, 继续执行业务逻辑. (注意: 在正常执行之前先删除token)
	不在表示重复请求, 暂停操作返回重复提示.
	
# 缺点: 额外的请求
'''

2. 乐观锁机制

# 介绍
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制实现。

# 适用范围 
乐观锁适用于多读的应用类型,这样可以提高吞吐量


# 版本号机制
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。
当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,
若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

举例:

假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。当需要对账户信息表进行更新的时候,需要首先读取version字段。

  1. 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
  2. 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
  3. 操作员 A 完成了修改工作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,一致的话,就会将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
  4. 操作员 B 完成了操作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,而自己读取到的版本号为1 ,不满足 “ 当前最后更新的version与操作员第一次读取的版本号相等 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。

这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。

参考: https://blog.csdn.net/qq_34337272/article/details/81072874

注意: 版本号其实并不是完美的解决方案, 一旦发上高并发的时候,就只有一个线程可以修改成功,那么就会存在大量的失败。对于像淘宝这样的电商网站,高并发是常有的事,总让用户感知到失败显然是不合理的。所以,还是要想办法减少乐观锁的力度的. 可以通过最大程度的提升吞吐率,进而提高并发能力来达到这种目的!

参考: https://www.sohu.com/a/306900091_250298

posted @ 2020-08-16 10:44  给你加马桶唱疏通  阅读(552)  评论(0编辑  收藏  举报