如何实现幂等性

什么是幂等

幂等本来是数学上的概念,它的定义是这样的: 如果一个函数 f(x) 满足:f(f(x)) = f(x),则函数 f(x) 满足幂等性。比如,求绝对值的函数,abs(x) = abs(abs(x))。

在计算机领域用来描述一个操作、方法或者服务。一个幂等操作的特点是,其任意多次执行所产生的影响均与一次执行的影响相同。

非幂等接口带来的问题

我们把系统解耦隔离后,服务间的调用可能会有三个状态,一个是成功(Success),一个是失败(Failed),一个是超时(Timeout)。前两者都是明确的状态,而超时则是完全不知道是什么状态。比如,超时原因是网络传输丢包的问题,可能是请求时就没有请求到,也有可能是请求到了,返回结果时没有正常返回等等情况。于是我们完全不知道下游系统是否收到了请求,而收到了请求是否处理了,成功 / 失败的状态在返回时是否遇到了网络问题。总之,请求方完全不知道是怎么回事。
举几个例子:

  • 订单创建接口,第一次调用超时了,然后调用方重试了一次。是否会多创建一笔订单?
  • 订单创建时,我们需要去扣减库存,这时接口发生了超时,调用方重试了一次。是否会多扣一次库存?
  • 当这笔订单开始支付,在支付请求发出之后,在服务端发生了扣钱操作,接口响应超时了,调用方重试了一次。是否会多扣一次钱?

因为系统超时,而调用方重试一下,会给我们的系统带来不一致的副作用。
在这种情况下,一般有两种处理方式。

  • 一种是需要下游系统提供相应的查询接口。上游系统在 timeout 后去查询一下。如果查到了,就表明已经做了,成功了就不用做了,失败了就走失败流程。
  • 另一种是通过幂等性的方式。也就是说,把这个查询操作交给下游系统,我上游系统只管重试,下游系统保证一次和多次的请求结果是一样的。

对于第一种方式,需要对方提供一个查询接口来做配合。而第二种方式则需要下游的系统提供支持幂等性的交易接口。


场景

将林志玲账户的余额加 100 元

方法一(推荐使用): 令牌机制(全局ID) (记录并检查操作)

在发送消息时,给每条消息指定一个全局唯一的ID,消费时,先根据这个ID检查这条消息是否有被消费过,如果没有消费过更新数据,然后将消费状态置为已消费。

  1. 服务器端派发token并将token记录在缓存中, 客户端携带此token请求服务, 如果此token有效证明是有效请求处理请求, 并删除token。token无效忽略此请求。
  2. 客户端请求携带一个唯一标识(流水号),服务器判断此唯一标识是否已经存在,如果已存在忽略此请求。
方法二:利用数据库的唯一约束实现幂等(利用数据库实现幂等性)

我们在数据库中建一张转账流水表,这个表有三个字段:转账单 ID、账户 ID 和变更金额,然后给转账单 ID 和账户 ID 这两个字段联合起来创建一个唯一约束,这样对于相同的转账单 ID 和账户 ID,表里至多只能存在一条记录。

方法三:为更新的数据设置前置条件(将方法实现幂等)
  1. 方法入参传入林志玲的账户余额,拿参数中的余额与数据库中的余额做比较如果相同则执行变更操作。
  2. 最简单的做法给数据增加一个版本号属性,每次更改数据前,比较当前数据的版本号是否和消息中的版本一致,如果不一致就拒绝更新数据,更新数据的同时将版本号 1。(和乐观锁原理一样)

实现幂等的核心是判断请求是否重复,具体实现方式比较多,想设计一个高可用的幂等方法还需要我们具体业务具体分析具体设计。

其他方式

1.将请求参数通过md5加密或者hashcode()计算后值存入缓存
2.添加个版本号,当前提交版本号大于上次提交版本号


HTTP的幂等性

HTTP GET方法用于获取资源,不应有副作用,所以是幂等的。 比如: GET

http: / /www . bank . com/ account/123456,不会改变资源的状态,不论调用一次还是 N次都没有副作用。请注意,这里强调的是- -次和N次具有相同的副作用,而不是每次GET的结果相同。GET http:/ /www. news . com/latest-news这个HTTP请求可能会每次得到不同的结果,但它本身并没有产生任何副作用,因而是满足幂等性的。

HTTP HEAD和GET本质是一样的,区别在于HEAD不含有呈现数据,而仅仅是HTTP头信息,不应用有副作用,也是幂等的。 有的人可能觉得这个方法没什么用,其实不是这样的。想象一个业务情景:欲判断某个资源是否存在,我们通常使用GET,但这里用HEAD则意义更加明确。也就是说,HEAD 方法可以用来做探活使用。

HTTP OPTIONS主要用于获取当前URL所支持的方法,所以也是幂等的。 若请求成功,则.它会在HTTP头中包含一个名为“Allow”的头,值是所支持的方法,如“GET, POST"。

HTTP DELETE方法用于删除资源,有副作用,但它应该满足幂等性。 比如: DELETE
http://www. forum. com/article/4231,调用一次和N次对系统产生的副作用是相同的,即删掉ID为4231的帖子。因此,调用者可以多次调用或刷新页面而不必担心引起错误。

HTTP POST方法用于创建资源,所对应的URI并非创建的资源本身,而是去执行创建动作的操作者,有副作用,不满足幂等性。 比如: POST http:/ /www. forum. com/ articles的语义是在http: //www. forum. com/articles下创建一篇帖子, HTTP响应中应包含帖子的创建状态以及帖子的URI。两次相同的POST请求会在服务器端创建两份资源,它们具有不同的URI;所以,POST方法不具备幂等性。

HTTP PUT方法用于创建或更新操作,所对应的URI是要创建或更新的资源本身,有副作用,它应该满足幂等性。 比如: PUT http: / /www. forum/articles/4231的语义是创建或更新ID为4231的帖子。对同一URI进行多次PUT的副作用和一次PUT是相同的;因此,PUT方法具有幂等性。

所以,对于POST的方式,很可能会出现多次提交的问题,就好比,我们在论坛中发贴时,有时候因为网络有问题,可能会对同一篇贴子出现多次提交的情况。对此的一-般的幂等性的设计如下。

  • 首先,在表单中需要隐藏一个token,这个token可以是前端生成的一个唯一的ID。用 于防止用户多次点击了表单提交按钮,而导致后端收到了多次请求,却不能分辨是否是重复的提交。这个token是表单的唯一标识。 (这种情况其实是 通过前端生成ID把POST变成了PUT。)
  • 然后,当用户点击提交后,后端会把用户提示的数据和这个token保存在数据库中。如果有重复提交,那么数据库中的token会做排它限制,从而做到幂等性。
  • 当然,更为稳妥的做法是,后端成功后向前端返回302跳转,把用户的前端页跳转到GET请求,把刚刚POST的数据给展示出来。如果是Web.上的最好还把之前的表单设置成过期,这样用户不能通过浏览器后退按钮来重新提交。这个模式又叫做OPRG模式(Post/Redirect/Get)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘彦青-Yannis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值