接⼝幂等性
⼀. 什么是幂等性?
在数学⾥,幂等有两种主要的定义。
在某⼆元运算下,幂等元素是指被⾃⼰重复运算的结果等于它⾃⼰的元素。例如,乘法下唯⼀两个幂等为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. 乐观锁机制
# 介绍
restful接口调用实例总是假设最好的情况,每次去拿数据的时候都认为别⼈不会修改,所以不会上锁,但是在更新的时候会判断⼀下在此期间别⼈有没有去更新这个数据,可以使⽤版本号机制实现。
# 适⽤范围
乐观锁适⽤于多读的应⽤类型,这样可以提⾼吞吐量
# 版本号机制
⼀般是在数据表中加上⼀个数据版本号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 的操作结果的可能。
注意: 版本号其实并不是完美的解决⽅案, ⼀旦发上⾼并发的时候,就只有⼀个线程可以修改成功,那么就会存在⼤量的失败。对于像淘宝这样的电商⽹站,⾼并发是常有的事,
总让⽤户感知到失败显然是不合理的。所以,还是要想办法减少乐观锁的⼒度的. 可以通过最⼤程度的提升吞吐率,进⽽提⾼并发能⼒来达到这种⽬的!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论