加载中...
avatar
文章
42
标签
25
分类
21
首页
Java
Spring全家桶
  • Spring
  • SpringBoot
  • SpringCloud
JVM
源码
  • Mybatis
  • HashMap
归档
其他
  • 互联网电子书汇总
  • JAVA八股文指南
  • 历史
  • 相册
关于
Logo码农Stormling面试系列(三)| 幂等性
搜索
首页
Java
Spring全家桶
  • Spring
  • SpringBoot
  • SpringCloud
JVM
源码
  • Mybatis
  • HashMap
归档
其他
  • 互联网电子书汇总
  • JAVA八股文指南
  • 历史
  • 相册
关于

面试系列(三)| 幂等性

发表于2021-10-09|更新于2025-01-07|面试
|总字数:2.1k|阅读时长:6分钟|浏览量:

幂等性

​ HTTP/1.1 中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影响相同。

这里需要关注几个重点:

  1. 幂等不仅仅只是一次(或多次)请求对资源没有副作用(比如查询数据库操作,没有增删改,因此没有对数据库有任何影响)。

  2. 幂等还包括第一次请求的时候对资源产生了副作用,但是以后的多次请求都不会再对资源产生副作用。

  3. 幂等关注的是以后的多次请求是否对资源产生的副作用,而不关注结果。

  4. 网络超时等问题,不是幂等的讨论范围。

幂等性是系统服务对外一种承诺(而不是实现),承诺只要调用接口成功,外部多次调用对系统的影响是一致的。声明为幂等的服务会认为外部调用失败是常态,并且失败之后必然会有重试。

什么情况下需要幂等

业务开发中,经常会遇到重复提交的情况,无论是由于网络问题无法收到请求结果而重新发起请求,或是前端的操作抖动而造成重复提交情况。 在交易系统,支付系统这种重复提交造成的问题有尤其明显,比如:

  1. 用户在 APP 上连续点击了多次提交订单,后台应该只产生一个订单;

  2. 向支付宝发起支付请求,由于网络问题或系统 BUG 重发,支付宝应该只扣一次钱。 很显然,声明幂等的服务认为,外部调用者会存在多次调用的情况,为了防止外部多次调用对系统数据状态的发生多次改变,将服务设计成幂等。

幂等 VS 防重

上面例子中小明遇到的问题,只是重复提交的情况,和服务幂等的初衷是不同的。重复提交是在第一次请求已经成功的情况下,人为的进行多次操作,导致不满足幂等要求的服务多次改变状态。而幂等更多使用的情况是第一次请求不知道结果(比如超时)或者失败的异常情况下,发起多次请求,目的是多次确认第一次请求成功,却不会因多次请求而出现多次的状态变化。

什么情况下需要保证幂等性

以 SQL 为例,有下面三种场景,只有第三种场景需要开发人员使用其他策略保证幂等性:

  1. SELECT col1 FROM tab1 WHER col2=2,无论执行多少次都不会改变状态,是天然的幂等。

  2. UPDATE tab1 SET col1=1 WHERE col2=2,无论执行成功多少次状态都是一致的,因此也是幂等操作。

  3. UPDATE tab1 SET col1=col1+1 WHERE col2=2,每次执行的结果都会发生变化,这种不是幂等的。

为什么要设计幂等性的服务

幂等可以使得客户端逻辑处理变得简单,但是却以服务逻辑变得复杂为代价。满足幂等服务的需要在逻辑中至少包含两点:

  1. 首先去查询上一次的执行状态,如果没有则认为是第一次请求

  2. 在服务改变状态的业务逻辑前,保证防重复提交的逻辑

幂等的不足

幂等是为了简化客户端逻辑处理,却增加了服务提供者的逻辑和成本,是否有必要,需要根据具体场景具体分析,因此除了业务上的特殊要求外,尽量不提供幂等的接口。

  1. 增加了额外控制幂等的业务逻辑,复杂化了业务功能;

  2. 把并行执行的功能改为串行执行,降低了执行效率。

保证幂等策略

幂等需要通过唯一的业务单号来保证。也就是说相同的业务单号,认为是同一笔业务。使用这个唯一的业务单号来确保,后面多次的相同的业务单号的处理逻辑和执行效果是一致的。 下面以支付为例,在不考虑并发的情况下,实现幂等很简单:
①先查询一下订单是否已经支付过,
②如果已经支付过,则返回支付成功;如果没有支付,进行支付流程,修改订单状态为‘已支付’。

防重复提交策略

上述的保证幂等方案是分成两步的,第②步依赖第①步的查询结果,无法保证原子性的。在高并发下就会出现下面的情况:第二次请求在第一次请求第②步订单状态还没有修改为‘已支付状态’的情况下到来。既然得出了这个结论,余下的问题也就变得简单:把查询和变更状态操作加锁,将并行操作改为串行操作。

乐观锁

如果只是更新已有的数据,没有必要对业务进行加锁,设计表结构时使用乐观锁,一般通过 version 来做乐观锁,这样既能保证执行效率,又能保证幂等。例如:

UPDATE tab1 SET col1=1,version=version+1 WHERE version=#version#

 不过,乐观锁存在失效的情况,就是常说的 ABA 问题,不过如果 version 版本一直是自增的就不会出现 ABA 的情况。(从网上找了一张图片很能说明乐观锁,引用过来,出自 Mybatis 对乐观锁的支持)

防重表

使用订单号 orderNo 做为去重表的唯一索引,每次请求都根据订单号向去重表中插入一条数据。第一次请求查询订单支付状态,当然订单没有支付,进行支付操作,无论成功与否,执行完后更新订单状态为成功或失败,删除去重表中的数据。后续的订单因为表中唯一索引而插入失败,则返回操作失败,直到第一次的请求完成(成功或失败)。可以看出防重表作用是加锁的功能。

分布式锁

这里使用的防重表可以使用分布式锁代替,比如 Redis。订单发起支付请求,支付系统会去 Redis 缓存中查询是否存在该订单号的 Key,如果不存在,则向 Redis 增加 Key 为订单号。查询订单支付已经支付,如果没有则进行支付,支付完成后删除该订单号的 Key。通过 Redis 做到了分布式锁,只有这次订单订单支付请求完成,下次请求才能进来。相比去重表,将放并发做到了缓存中,较为高效。思路相同,同一时间只能完成一次支付请求。

token 令牌

这种方式分成两个阶段:申请 token 阶段和支付阶段。 第一阶段,在进入到提交订单页面之前,需要订单系统根据用户信息向支付系统发起一次申请 token 的请求,支付系统将 token 保存到 Redis 缓存中,为第二阶段支付使用。 第二阶段,订单系统拿着申请到的 token 发起支付请求,支付系统会检查 Redis 中是否存在该 token,如果存在,表示第一次发起支付请求,删除缓存中 token 后开始支付逻辑处理;如果缓存中不存在,表示非法请求。 实际上这里的 token 是一个信物,支付系统根据 token 确认,你是你妈的孩子。不足是需要系统间交互两次,流程较上述方法复杂。

支付缓冲区

把订单的支付请求都快速地接下来,一个快速接单的缓冲管道。后续使用异步任务处理管道中的数据,过滤掉重复的待支付订单。优点是同步转异步,高吞吐。不足是不能及时地返回支付结果,需要后续监听支付结果的异步返回。

文章作者: stormling
文章链接: http://www.stormling.top/posts/16712.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 码农Stormling!
幂等性
cover of previous post
上一篇
面试系列(四)| 虚拟机JVM
JVM 的主要作用是什么?JVM 就是 Java Virtual Machine(Java 虚拟机)的缩写,JVM 屏蔽了与具体操作系统平台相关的信息,使 Java 程序只需生成在 Java 虚拟机上运行的目标代码 (字节码),就可以在不同的平台上运行。 据验证Java 的内存区域?JVM 在执行 Java 程序的过程中会把它管理的内存分为若干个不同的区域,这些组成部分有些是线程私有的,有些则是线程共享的,Java 内存区域也叫做运行时数据区,它的具体划分如下: 虚拟机栈 : Java 虚拟机栈是线程私有的数据区,Java 虚拟机栈的生命周期与线程相同,虚拟机栈也是局部变量的存储位置。方法在执行过程中,会在虚拟机栈中创建一个 栈帧(stack frame)。每个方法执行的过程就对应了一个入栈和出栈的过程。 本地方法栈: 本地方法栈也是线程私有的数据区,本地方法栈存储的区域主要是 Java 中使用 native...
cover of next post
下一篇
面试系列(二)| SpringCloud 面试题
微服务基础1.什么是微服务架构​ 微服务架构就是将单体的应用程序分成多个应用程序,这多个应用程序就成为微服务,每个微服务 运行在自己的进程中,并使用轻量级的机制通信。这些服务围绕业务能力来划分,并通过自动化部 署机制来独立部署。这些服务可以使用不同的编程语言,不同数据库,以保证最低限度的集中式管理。 2.为什么需要学习Spring Cloud 首先Spring Cloud基于Spring Boot的优雅简洁,可还记得我们被无数xml支配的恐惧?可还记得 Spring MVC ,Mybatis 错综复杂的配置,有了Spring Boot,这些东西都不需要了,Spring Boot好处不再赘诉,Spring Cloud就基于Spring Boot把市场上优秀的服务框架组合起来,通过Spring Boot风 格进行再封装屏蔽掉了复杂的配置和实现原理 什么叫做开箱即用?即使是当年的黄金搭档 Dubbo + ZooKeeper下载配置起来也是颇费心神的!而 Spring Cloud完成这些只需要一个jar的依赖就可以了! Spring Cloud大多数子模块都是直击痛点,像...

评论
ValineGitalk
avatar
stormling
文章
42
标签
25
分类
21
Follow Me
公告
欢迎大家来到Stormling博客
目录
  1. 1. 幂等性
    1. 1.1. 什么情况下需要幂等
    2. 1.2. 幂等 VS 防重
    3. 1.3. 什么情况下需要保证幂等性
    4. 1.4. 为什么要设计幂等性的服务
    5. 1.5. 幂等的不足
    6. 1.6. 保证幂等策略
    7. 1.7. 防重复提交策略
      1. 1.7.1. 乐观锁
      2. 1.7.2. 防重表
      3. 1.7.3. 分布式锁
      4. 1.7.4. token 令牌
      5. 1.7.5. 支付缓冲区
最新文章
面向八股文面试专场
面向八股文面试专场2025-01-22
【每日早报】-2025-01-21 - 星期二
【每日早报】-2025-01-21 - 星期二2025-01-21
规则引擎 Drools 8+ 快速入门
规则引擎 Drools 8+ 快速入门2024-12-11
数据库系列(二) | Mybatis Plus 3.0+快速入门
数据库系列(二) | Mybatis Plus 3.0+快速入门2024-12-09
分布式系列(二) | Redisson分布式锁
分布式系列(二) | Redisson分布式锁2024-12-05
©2019 - 2025 By stormling
码农Stormling程序员,关注公众号【码农Stormling】回复【面试】获取最全面试pdf
搜索
数据加载中