面试官:如何设计幂等性接口

什么是幂等性?

所谓幂等性,就是一次操作和多次操作同一个资源,所产生的影响均与一次操作的影响相同。

"幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。

幂等性,用数学语言表达就是:

f(x)=f(f(x))

维基百科的幂等性定义如下:

幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。

幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。

这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。

例如,“setTrue()”函数就是一个幂等函数,无论多次执行,其结果都是一样的,更复杂的操作幂等保证是利用唯一交易号(流水号)实现.

 在软件或者系统中,重复使用幂等函数或幂等方法不会影响系统状态,也不用担心重复执行会对系统造成改变。

通俗点说:

一个接口如果幂等,不管被调多少次,只要参数不变,结果也不变。

幂等性是对于写操作来说的,一个写操作,一般都需要保证:

  • 幂等性

  • 可用性

  • ACID事务属性。

当然,这里仅仅聚焦 幂等。

为什么需要幂等性? 

如果客户端重复调用,服务端会遇到如下的很多问题:

  1. 创建订单时,重复调用是否产生两笔订单?

  2. 扣减库存时,重复调是否会多扣一次?

这就是出现了幂等性问题。

按照幂等性要求,需要保证一次请求和多次请求同一个资源产生相同的副作用。

所以:创建订单时,重复调用是否产生两笔订单?当然不能。

所以:扣减库存时,重复调是否会多扣一次?当然不能。

这些,都是需要幂等性机制去保障。如果不支持幂等操作,那将会出现以下情况:

  • 电商超卖现象

  • 重复转账、扣款或付款

  • 重复增加金币、积分或优惠券

等等,非常惨的。

什么样的原因导致幂等性问题?

原因之一:底层网络阻塞和延迟的问题 

在系统高并发的环境下,很有可能因为网络阻塞等等问题,导致客户端不能及时的收到服务端响应,甚至是调用超时。这时候用户会重复点击,重复请求。

在消息队列组件中,客户端也有重试机制,如果投递失败/投递超时,则会重新投递。对于服务端来说,可能会收到重复投递的一份消息。

在RPC组件中,客户端也有重试机制,如果投递失败/投递超时,则会重试调用。对于服务端来说,可能会重复收到通用的调用。

原因之二:用户层面的重复操作

比如下单的按键在点按之后,在没有收到服务器请求之前,用户还可以被按。

或者,用户的App闪退/人工强退,之后重新打开重新下单

需要幂等性的 两大场景

可能会发生重复请求或重试操作的场景,在分布式、微服务架构中是随处可见的。

  • 网络波动:因网络波动,可能会引起重复请求

  • 分布式消息消费:任务发布后,使用分布式消息服务来进行消费

  • 用户重复操作:用户在使用产品时,可能无意地触发多笔交易,甚至没有响应而有意触发多笔交易

  • 未关闭的重试机制:因开发人员、测试人员或运维人员没有检查出来,而开启的重试机制(如Nginx重试、RPC通信重试或业务层重试等)

大致可以分为两大类:

  • 第一类:单数据CRUD操作的幂等性保证方案

  • 第二类:多数据并发操作的幂等性保证方案

第一类:单数据CRUD操作的幂等性保证方案

首先,来看看单数据CRUD操作的幂等性保证方案

对于单数据CRUD操作,很多具备天然幂等性

  • 新增类动作:不具备幂等性

  • 查询类动作:重复查询不会产生或变更新的数据,查询具有天然幂等性

  • 更新类动作:

    • 基于主键的计算式Update,不具备幂等性,即UPDATE goods SET number=number-1 WHERE id=1

    • 基于主键的非计算式Update:具备幂等性,即UPDATE goods SET number=newNumber WHERE id=1

    • 基于条件查询的Update,不一定具备幂等性(需要根据实际情况进行分析判断)

  • 删除类动作:

    • 基于主键的Delete具备幂等性

    • 一般业务层面都是逻辑删除(即update操作),而基于主键的逻辑删除操作也是具有幂等性的

大家看到,对于单数据CRUD操作, 只有在下面的三个场景,保证幂等即可:

  • 主键的计算式Update

  • 基于条件查询的Update

  • 新增类动作

第二类:多数据并发操作的幂等性保证方案

大部分,都是这种场景。

现在的应用,大部分都是微服务的。并且一个操作会涉及到多个数据的并发操作,会通过RPC调用到多个微服务。

分为两种情况:

  • 多数据同步操作,一般是服务端提供一个统一的同步操作api,客户端调用该api完成,直接获得操作结果。

  • 多数据异步操作,由于同步操作性能低,在高并发场景都会同步变异步,于是乎,服务端还要额外提供一个查询操作结果的api,去查询结果。第一次超时之后,调用方调用查询接口,如果查到了就走成功的流程,失败了就走失败的流程。

多数据并发操作的经典场景,参考如下:
1. 高并发抢红包

在抢一份红包的时候,点击了抢,开始异步抢红包。

抢到就有,没抢到就没有。

抢完之后,无论我们重复点击多少次,红包都会提示你已经抢过该红包了。

2. 高并发下单

高并发下单的一个很基本的问题,就是要避免重复订单。

如果用户操作一次,由于超时重试等原因,一看下了两个单,甚至10个重复单。

用户不晕倒在厕所才怪。

3. 高并发支付

在支付场景,支付平台会生成唯一的支付连接,不会再次生成另外的支付连接。

如何保证幂等呢 ?

幂等性的的确保方案,非常多,大致如下图所示

一些基础性的幂等性解决方案

全局唯一ID

如果使用全局唯一ID,就是根据业务的操作和内容生成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。

如果不存在则把全局ID,存储到存储系统中,比如数据库、Redis等。如果存在则表示该方法已经执行。

使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务操作。

一般情况下,对分布式的全局唯一id,可以参考以下几种方式:

  • UUID

  • Snowflake

  • 数据库自增ID

  • 业务本身的唯一约束

  • 业务字段+时间戳拼接

唯一索引(去重表)

这种方法适用于在业务中有唯一标识的插入场景中,比如在以上的支付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识

这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据写入去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。

插入或更新(upsert)

这种方法插入并且有唯一索引的情况,比如我们要关联商品品类,其中商品的ID和品类的ID可以构成唯一索引,并且在数据表中也增加了唯一索引。这时就可以使用InsertOrUpdate操作。

多版本控制

这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等:

boolean updateGoodsName(int id,String newName,int version);

在实现时可以如下:

update goods set name=#{newName},version=#{version} where id=#{id} and version<${version}

状态机控制

这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使用int类型,并且通过值类型的大小来做幂等,比如订单的创建为0,付款成功为100,付款失败为99。

在做状态机更新时,我们就可以这样控制:

update goods_order set status=#{status} where id=#{id} and status<#{status}

以上就是保证接口幂等性的一些方法。

综合性的解决方案:一锁二判三更新

前面的方案,都是一些基础性的方案。

在实际的业务中,一般会结合起来使用。

在双11和双12的活动中,对于幂等性问题,支付宝团队摸索出来了一个综合性的解决方案:一锁二判三更新。这个方案,可以作为一个比较通用的综合性的幂等解决方案。

何为“一锁二判三更新”?

简单来说就是当任何一个并发请求过来的时候

  • 1. 先锁定单据

  • 2. 然后判断单据状态,是否之前已经更新过对应状态了

  • 3.1  如果之前并没有更新,则本次请求可以更新,并完成相关业务逻辑。

  • 3.2  如果之前已经有更新,则本次不能更新,也不能完成业务逻辑。

 一锁、二判、三更性的核心步骤

第一步:先加锁。

高并发场景,建议是redis分布式锁,而不是低性能的DB锁,也不是CP型的 Zookeeper锁。

如果普通的redis分布式锁性能太低,该如何?

还可以考虑引入 锁的分段机制, 比如内部分成100端,总体上,就大概能线性提升 100倍。

第二步:进行幂等性判断。

幂等性判断,就是 进行 数据检查。

可以基于状态机、流水表、唯一性索引等等前面介绍的 基础方案,进行重复操作的判断。

第三步:数据更新

如果通过了第二步的幂等性判断, 说明之前没有执行过更新操作。

那么就进入第三步,进行数据的更新,将数据进行持久化。

操作完成之后, 记得释放锁, 结束整个流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/397135.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu解决“E: Unable to locate package lrzsz“

今天在ubuntu上安装rzsz包时报错&#xff0c;提示无法定位包&#xff0c;提示如下 出现这个问题是因为apt的源没有更新&#xff0c;我们直接说解决办法 把下面的命令执行一遍即可 sudo add-apt-repository main sudo add-apt-repository universe sudo add-apt-repository re…

物流EDI:Verizon EDI 需求分析

作为物流行业的企业&#xff0c;Verizon与其供应商之间通过EDI来传输业务单据。在与Verizon建立EDI连接时&#xff0c;需要参考EDI 指南、采购订单条款和条件以及运输路线指南这三个文档。 点击此链接&#xff0c;获取上述的三个文档 Verizon供应商可以通过上述链接找到用于处…

ThreadLocal用法

一.项目需求 在我们进行新增用户时,会涉及到创建人和修改人字段如何获取的问题.我们不可能再后端将这两个字段写成静态的值. 1.1 解决方案 通过某种方式动态获取当前登录员工的id 员工登录成功后会生成JWT令牌并响应给前端: /*** 员工管理*/ RestController RequestMapping(&q…

【软考问题】-- 2 - IT知识 - 信息技术发展

一、基本问题 2 - IT知识 - 信息技术发展 问题1:数据库根据存储方式可以分为什么? 数据结构模型 层次模型:最早使用的 一种模型,它用 “树 ” 结构表示实体集之间的关联,其中实体集(用矩形框表示)为结点,而树中各结点之间的连线表示它们之间的关联。格式化数据模型 网状…

CDC 整合方案:MySQL > Flink CDC > Kafka > Hudi

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

力扣145 二叉树的后序遍历 Java版本

文章目录 题目描述递归解法代码 非递归解法思路代码 题目描述 给你一棵二叉树的根节点 root &#xff0c;返回其节点值的 后序遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[3,2,1] 示例 2&#xff1a; 输入&#xff1a;root [] 输出…

log4j2的使用

基础用法 1. pom文件导入依赖 junit用来做测试 <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.5</version></dependency><dependency><groupId>org.…

第五次作业(防御安全)

需求: 1.办公区设备可以通过电信链路和移动链路上网&#xff08;多对多的NAT&#xff0c;并且需要保留一个公网IP 不能用来转换&#xff09; 2.分公司设备可以通过总公司的移动链路和电信链路访问到DMZ区的http服务器 3.分公司内部的客户端可以通过公网地址访问到内部的服务…

两大公示 总结先行先试经验,提炼可复制推广成果

2024年1月18日&#xff0c;水利部官网发布《数字孪生水利建设典型案例名录&#xff08;2023年&#xff09;》&#xff08;共28项&#xff0c;排名不分先后&#xff09;、《数字孪生水利建设十大样板名单&#xff08;2023年&#xff09;》&#xff08;排名不分先后&#xff09;等…

从数据库中读取文件导出为Excel

使用的库&#xff08;org.apache.poi&#xff09; 在poi包中有Apache提供的各种分类文件&#xff0c;如下 结构功能HSSF读写Microsoft Excel XLS文件XSSF读写Microsoft Excel OOXML XLSX文件HWPF读写Microsoft Word DOC文件HSLF读写Microsoft PowerPoint文件 下面以XSSF为例&…

代码随想录算法训练营29期|day55 任务以及具体安排

第九章 动态规划part12 309.最佳买卖股票时机含冷冻期 class Solution {public int maxProfit(int[] prices) {//0代表持股票&#xff0c;1代表保持卖出状态&#xff0c;2代表卖出股票。3代表冷冻int[][] dp new int[prices.length][4];dp[0][0] -prices[0];for(int i 1 ; …

Redis事务长什么样?一文带你全面了解

一、概述 1.1、Redis事务简介 在 Redis 中&#xff0c;事务是一组命令的有序队列&#xff0c;Redis 使用 MULTI、EXEC、WATCH 和 DISCARD 等命令来实现事务。事务的执行是原子的&#xff0c;要么所有命令都执行成功&#xff0c;要么所有命令都不执行。事务中的命令在 EXEC 执行…

Avalonia学习(二十五)-系统界面

目前项目式练习&#xff0c;界面内容偏多&#xff0c;所以不给大家贴代码了&#xff0c;可以留言交流。此次为大家展示的是物联项目的例子&#xff0c;仅仅是学习&#xff0c;我把一些重点列举一下。 界面无边框 同前面 treeview控件 通过treevie控件导航 tabcontrol控件 …

AUTOSAR CP--chapter7从CAN网络学习Autosar通信

从CAN网络学习Autosar通信 前言缩写词CAN通信在AUTOSAR架构中的传输上位机配置 第六章总结&#xff1a;学习了如何使用工具的自动配置功能&#xff0c;位我们生成系统描述中部分ecu的BSW模块配置&#xff0c;但是自动配置的功能虽然为我们提供了极大的便利&#xff0c;我们仍然…

css3的var()函数

css3的var()函数 变量要以两个连字符--(横杆)(减号)为开头 变量可以在:root{}中定义, :root可以在css中创建全局样式变量。通过 :root本身写的样式&#xff0c;相当于 html&#xff0c;但优先级比后者高。 在CSS3中&#xff0c;var()函数是一个用于插入CSS自定义属性&#xff…

Active Directory 的密码管理策略

员工使用的密码可以决定或破坏组织中的数据安全性&#xff0c;但是&#xff0c;知道员工通常不遵循良好的密码卫生习惯也就不足为奇了。从在本机工具&#xff08;如 Windows Active Directory 组策略&#xff09;中设置弱密码和通用密码到宽松的密码策略规则&#xff0c;有几个…

Eclipse - Text Editors (文本编辑器)

Eclipse - Text Editors [文本编辑器] References Window -> Preferences -> General -> Editors -> Text Editors Displayed tab witdth: 4 勾选 Insert spaces for tabs 勾选 Show line number References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.n…

学习总结19

# 奶牛的耳语 ## 题目描述 在你的养牛场&#xff0c;所有的奶牛都养在一排呈直线的牛栏中。一共有 n 头奶牛&#xff0c;其中第 i 头牛在直线上所处的位置可以用一个整数坐标 pi(0< pi < 10^8) 来表示。在无聊的日子里&#xff0c;奶牛们常常在自己的牛栏里与其它奶牛交…

【Azure 架构师学习笔记】- Azure Databricks (7) --Unity Catalog(UC) 基本概念和组件

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Databricks】系列。 接上文 【Azure 架构师学习笔记】- Azure Databricks (6) - 配置Unity Catalog 前言 在以前的Databricks中&#xff0c;主要由Workspace和集群、SQL Warehouse组成&#xff0c; 这两年Databricks公…

NLP_BERT与GPT争锋

文章目录 介绍小结 介绍 在开始训练GPT之前&#xff0c;我们先比较一下BERT和 GPT 这两种基于 Transformer 的预训练模型结构&#xff0c;找出它们的异同。 Transformer架构被提出后不久&#xff0c;一大批基于这个架构的预训练模型就如雨后春笋般地出现了。其中最重要、影响…