如何保证幂等性

什么是幂等性

幂等性:概念源于数学,原意是指一个操作如果连续执行多次所产生的结果与仅执行一次的效果相同,那么我们就称这个操作是幂等的。

调用方,对一个系统进行重复调用(参数全部相同),不论重复调用多少次,这些调用对系统的影响都是相同的效果。不会随着次数的变化而改变。

举例:

  1. 幂等性

假设对象 Person中 有个 name 属性,有个 setName 方法

setName(String name){
   this.name = name
}

那这个方法就是天然幂等的,你输入相同的参数,不论你重复调用多少次都是将名字设置为“小明”,其对对象Person的影响都是一样的。这就是天然幂等性。

  1. 非幂等性

还是拿对象 Person 举例子,假设对象中有个age属性,有个increaseAge 方法

increaseAge(){
   this.age++;
}

我们按正常的步骤一次一次调用是不会有问题的,如果调用者没有控制好逻辑,一次流程重复调用好几次,这时候影响效果和一次是有非常大区别,代码编写者以为它只会调用一次,结果出现了意外调用了很多次,恰好方法不具有幂等性,于是就会出现和预期不一样的效果。

这个方法本身是不具备幂等性的,我们可以修改这个方法,让其传入一个标识符,每一次重复的请求会有相同的标识符,方法内部可以根据标识符查数据库是不是已经处理过,如果处理过就不重复处理。这样方法就具备了幂等性。

  1. 作用

比如我们常常遇到的订单创建,支付等业务。

  • 如果一个“创建订单”接口实现了幂等性,当收到两次同样的创建请求时,系统应该要么拒绝第二个请求(因为它已经是重复请求),要么确保只有一个订单被创建,而不是两个完全一样的订单。
  • 对于一个“支付”接口,幂等性要求即便用户由于网络原因反复点击支付按钮,服务端也只会扣除用户账户一次金额,避免重复扣费。

导致幂等性问题的原因

  1. 网络波动不稳定
    网络通信中的丢包、延迟等情况可能导致客户端未收到服务端的响应或服务端未收到客户端的请求,此时客户端可能会重试发送请求,导致接口被重复调用。
  2. 用户操作
    用户快速重复点击导致

    用户在等待响应时,由于不确定是否操作成功,可能会多次点击提交按钮,进而发送多次相同的请求。

    用户频繁刷新页面

    尤其是在某些提交操作尚未完成时,刷新页面可能会重新发送请求。

    用户在浏览器上点击回退然后再重复之间的提交操作,

    这都可能会导致重新发送请求。
  3. 重试机制
    在高可用性设计中,客户端常常设置有重试机制,当请求失败或超时时会自动重新发起请求。而在分布式系统中,服务间调用也可能有重试策略,以应对临时故障。

    比如 Nginx 重试,RPC 重试,或者调用方业务层中进行重试。
  4. 定时任务或异步处理
    在定时任务中,如果定时任务调度或逻辑设计不当,可能会导致同一任务被执行多次。

    在消息队列中,消息可能会因为异常等原因被重复消费。

  5. 并发控制
    缺乏有效的并发控制手段,导致在并发环境下,针对同一资源的操作被多次执行。

如何保证幂等性

总的来说,导致幂等性问题可以粗略的归类于两种情况:前端调用以及服务端调用

服务端控制

在服务端接口处理逻辑时,可以通过通过一些特定的标识符或请求参数来校验请求的幂等性,以确保同样的请求不会被重复处理。
  1. 唯一标识符

客户端每次发起请求会携带一个全局唯一的标识符。服务器接收到请求后就会对这个标识符进行检查,

若服务器发现该标识符已经在系统中存在,表明这是一个重复请求,此时服务器可以选择忽略该请求,或者向客户端返回已处理过相同请求的结果信息。

若服务器未找到该标识符存在于系统内,则认定该请求为新请求,服务器将继续对其进行正常处理,并将此唯一标识符保存至系统中,以便于后续对接收的请求进行有效性校验,防止同一请求的重复处理。

比如我们在要求上游 ERP 系统对接订单平台时就会要求上游传递一个账号下全局唯一的一个参考单号,这个参考单号一个很重要的作用就是保证接口幂等性。
  1. 请求参数

某些请求参数确实可以用来辅助校验请求的幂等性。

例如,时间戳可以作为一种可能的请求参数,在处理请求时,服务器可以通过比较时间戳与服务器当前时间来判断请求的有效性。若时间戳与当前时间之间的差异超出预设的合理范围(如几秒钟到几分钟不等,具体阈值视业务场景而定),服务器可以推测该请求可能是由于网络延迟或者其他原因导致的重复提交。

单纯依靠时间戳来判断幂等性和重复请求并不完全准确,因为不同的客户端时间可能并不精确同步,而且时间戳本身无法保证全局唯一性。但是它可以作为一种有效的辅助手段来减少重复处理的可能性
  1. 状态机设计

对于状态转移类的操作类型的业务,可采用状态机设计,每次请求只允许合法的状态变迁,非法状态变迁(如已经完成的订单不允许再次支付)将被拒绝。

  1. 乐观锁

在更新数据时,可以通过版本号时间戳等机制判断数据是否已被修改,防止因并发请求导致的多次更新问题。具体做法:

  • 在数据库表中增加一个版本号字段(version)或者时间戳字段(timestamp)。
  • 客户端第一次请求时获取数据的版本号或时间戳。
  • 客户端发起更新操作时,将上次读取的版本号或时间戳一起发送回服务器。
  • 服务器在执行更新操作前,首先检查当前数据库中的版本号或时间戳是否与客户端提交的一致。

    • 如果一致,说明在这期间数据没有被其他事务修改过,于是更新数据并递增版本号或更新时间戳。
    • 如果不一致,说明数据已经被修改过,此时服务器拒绝本次更新请求,返回错误提示,客户端可以根据错误信息决定是否重新获取最新数据再尝试更新。

通过这种方式,即使客户端因为网络原因或其他因素导致同一请求被多次发送,乐观锁机制能确保只有在数据未被其他事务修改的前提下,才会执行更新操作,从而达到接口幂等的效果。

前端调用

实现幂等性方案示例

附录

状态机设计模式

是一种行为型设计模式,用于描述对象在不同状态下的行为(在状态机模式中,对象的行为取决于其内部状态,并且在不同的状态下,对象可能会有不同的行为)。

核心思想:是将对象的行为与其状态解耦,使得状态之间的转换更加清晰和可控。状态机模式通常涉及定义一组状态以及状态之间的转换规则。


结构:

该模式主要包含以下几个要素:

  1. 状态(State):状态机模式中的状态表示对象所处的特定状态。每个状态都定义了对象在该状态下的行为。
  2. 上下文(Context):上下文是包含状态机的对象。它维护了当前状态,并在状态之间的转换发生时更新状态。
  3. 转换(Transition):转换描述了对象从一个状态转移到另一个状态的过程。它通常受到一些条件或触发事件的影响。
  4. 动作(Action):动作是状态转换期间可能执行的操作或行为。

状态机模式的核心思想是将对象的行为与其状态解耦,从而使得状态之间的转换更加清晰和可控。它有助于简化复杂系统的设计和实现,特别是当系统具有多个可能状态和状态之间的复杂转换关系时。

状态模式 | 菜鸟教程

如何保证接口幂等性 - 码农Academy - 博客园

什么是幂等性?如何解决幂等性问题?-CSDN博客

一口气说出四种幂等性解决方案,面试官都惊呆了!! - 知乎



pipiimmortal
13 声望0 粉丝