嘘~ 正在从服务器偷取页面 . . .

SpringCloud 幂等性问题


幂等性问题

幂等性概念

幂等 idempotency

幂等性是数学概念,常见于抽象代数中。表达的是N次变换与1次变换的结果相同。

方法调用一次和方法调用多此产生额外的效果是相同的,就是具有幂等性。在软件系统中,指使用相同参数重复执行,并能获得相同的结果的函数,这些函数不会影响系统状态,不用担心重复执行会对系统产生影响。

Http维度幂等性

HTTP维度的幂等性是指除了错误和过期以外,一次和多此请求某个资源应该具有同样的副作用。

GET/DELETE/PUT/POST

(1)GET方法用于获取资源,不应有副作用,所以是幂等的。

强调的是一次和N次请求的具有相同的副作用,不是结果相同。

(2)DELETE方法用于删除资源,有副作用,但它应该满足幂等性。

http中DELETE删除一般要求指定资源的幂等性。

(3)POST方法对应的URI为资源接受者。每次post请求会在后端创建资源,所以POST方法不具有幂等性。

(4)PUT方法所对应的URI是要创建资源或者更新资源。对同一个URI的进行1次或N次执行PUT,所以PUT方法具有幂等性。

HTTP应用

Restful 风格API

RPC 风格API

应用系统维度幂等性

系统中函数或接口可以使用相同参数重复调用执行,不影响系统状态,也不会对系统造成改变,任意次数的执行所产生的影响与一次执行产生的影响相同。

第一次请求的时候对资源产生了副作用,但是以后多此请求都不会再对资源产生副作用。不会对结果产生破坏或者产生不可预料的结果。

幂等性产生场景

分布式微服务架构。

(1)网络产生的重复请求。

(2)用户重复操作,无意触发多此下单多此交易。

(3)应用使用了失败或者超时重试机制。Nginx重试或Rpc重试

(4)第三方平台接口,如支付成功回调接口

(5)中间件/应用服务器重试机制

(6)用户双击提交按钮,刷新页面,表单重复提交(重复下单重复扣款)

(7)浏览器重复提交表单,重复HTTP请求

(8)定时任务的重置执行。

幂等性问题解决

在数据访问层实现比较合适

读请求

不会产生副作用,可以不做幂等性。

写请求

写请求会改变数据必须要做幂等性。

写入数据 INSERT操作

修改数据UPDATE操作

删除数据DELETE操作,分情况

幂等性实现解决方案

后端幂等性实现

(1)使用唯一索引防止幂等性问题

简单粗暴的防止重复写入数据,当数据重复插入是,数据库会抛异常,保证不会出现脏数据。

(2)Token 和 Redis 方案

该方案分两个阶段,申请Token和业务操作。

第一阶段,在提交订单之前,需要订单系统根据用户信息向支付系统发起一起申请token的请求,支付系统将token保存到Redis缓存中,为第二阶段的使用做准备。

第二阶段,订单系统拿着申请到的token发起支付请求,支付系统会检查Redis中是否存在该Token,如果存在表示第一次发起支付,执行支付逻辑,执行完成后删除Redis中的token。

当重复请求时,检查缓存中的token,不存在则表示非法请求。

不足:系统间交互2次。并发情况,针对时序问题,需要将验证Token + 执行业务 + 删除Token 几个步骤加锁,如果删除Token失效,还需要异步重删。

(3)状态机实现幂等

针对更新操作,如修改某些业务状态,在设计的时候只支持状态的单向改变,在更新的时候where条件里加上status=期望的原来的status,多此调用实际上也只能执行一次。

update table_xx set status=”支付中” where status=”待支付” and id=xxxx;

(4)乐观锁实现幂等

更新已有数据,可以进行加锁更新,也可以设计表结构的时候使用乐观锁,通过version来做乐观锁,这样既保证执行效率,又保证幂等,乐观锁的version版本在更新业务数据的时候要自增。

查询数据,得到版本号;通过版本号去更新,版本号匹配就更新,不匹配就不能更新

update table_xx set money=money -11,version =version +1 where id =xx and version =1

一次执行时,版本加1了,后续执行时,匹配不到,执行不会产生影响。

(5)防重表实现幂等

使用唯一主键去做防重表的唯一索引。

需要增加一张表,用来防止数据重复的,称之为防重表。其实利用数据库做分布式锁。

如使用订单号 orderNo 作为防重表的唯一索引,每次请求都根据订单号向防重表中插入一条数据,第一次请求查询订单支付状态,如果订单没有支付,进行支付操作,在进行支付操作之前向防重表插入该支付的订单号,插入成功说明可以支付,无论是否支付成功,执行完成后更新订单状态为成功或失败,然后可以删除防重表中的数据。后续的订单因为表中的唯一索引二插入失效,返回操作失败,直到第一次的请求操作完成(成功或者失败)。

防重表的作用是加锁的功能,可以防止并发重复。

但是删除防重表数据之后,依旧可以重复操作,串行重复无法阻止,需要结合状态机幂等。

(6)select + insert

该方式就是在操作之前先查询一下,符号要求再插入,该方案在没有并发的系统中可以解决幂等问题,在单JVM有并发的时候可以JVM加锁来保证幂等,在分布式环境下无法保证幂等性,可以使用分布式锁来保证。

(7)分布式锁保证幂等性

在执行某写操作之前,先获取锁,然后执行操作,如果没有获取到锁就等待锁释放直到获取锁。当执行操作完成,释放锁,关于锁超时,防止意外没有获取到锁,它可以用来解决分布式系统的幂等性。

常见分布式锁实现方案是redis,redisson,redLock,zookeeper等工具。

使用分布式锁类似于防重表,将防重并发放到缓存中,较为高效,思路相同,同一时间只能完成一次支付请求。

(8)缓冲队列实现幂等

将请求都快速的接收下来,放入缓冲队列,后续使用异步任务处理队列中的数据,过滤掉重复的请求。

优势:同步改异步,高吞吐量。

不足:不能及时返回请求结果,需要后续轮询处理结果。

(9)全局唯一号实现幂等

比如通过source来源+seq序列号,来判断请求是否重复,在并发时只能处理一个请求,其他相同并发请求要么返回请求重复,要么等待前面请求执行完成再执行。

幂等性虽然复杂化了业务功能和降低执行效率,但为了保证系统的正确性是必要的。

前端幂等性实现

(1)按钮只可操作一次

按钮置灰或loading状态。

该方案没有可靠性。

(2)token机制(比较老的方案)

产品上允许重复提交,但要保证重复提交不产生副作用,比如点击N此只产生一条记录;具体实现就是进入页面时申请一次token,然后后面所有的请求都带上这个token,根据token来避免重复请求。

(3)使用Post/Redirect/Get模式

提交后执行页面重定向,这就是PRG模式,当用户提交表单后去执行客户端的重定向,转到提交成功信息页面,这样可以避免F5刷新重复提交,不会出现浏览器表达重复提交的警告,也能消除浏览器前进和后退按钮导致重复提交问题。

(4)在Session中存放特殊标记

在服务器端,生成一个唯一标识符,将它存入session,同时将它写入表单隐藏域中,然后将表单页面发给浏览器,用户输入信息后提交表单,在服务器端,获取表单中隐藏字段的值,与session中的唯一标识符比较,相等说明是首次提交,就处理本次请求,然后将session



版权声明: 本博客所有文章除特別声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明来源 Small-Rose / 张小菜 !
评论
  目录