接口幂等性原理与方案总结

文章目录

  • 接口幂等概念
  • 典型场景
  • 核心解决方案
    • 一锁
    • 二判
    • 三更新
  • 方案选型对比

接口幂等概念

定义:无论调用接口多少次,对系统的影响与单次调用一样

范畴:在后端开发中,通常更关注写接口的幂等,因为写接口才会对系统数据造成不良影响;读接口多次调用,我们作为下游通常不好控制,读取到的数据不一样也更多是数据实时性导致的问题

典型场景

需要关注幂等性主要由于现代分布式系统面临三大不可靠因素:

  • 用户不可靠(手抖多点)
    用户在表单页多次点击按钮,前端提交了多次创建订单的请求
  • 网络不可靠(超时重传)
    当接口超时的时候,由网关层或者业务层控制重试次数,比方说第三方支付接口超时后通过指数回退算法计算重试间隔再重试
  • 系统不可靠(服务重试)
    MQ 中间件消费消息的时候,由于 Rebalance 或者消费失败重试等原因,同样的消息在被旧的消费者处理过后,有可能被再次分配到新的消费者身上继续处理

核心解决方案

核心流程可以总结为:一锁二判三更新,这三步每一步涉及到的方案会有分布式锁、乐观锁;Token机制、唯一索引、状态机、请求序列号等。

伪代码如下:

// 1. 一锁: 加上悲观锁或者乐观锁
Lock.lock();
try {// 2. 二判:判断请求是否已经被执行过Order order = orderService.queryOrder();if (order.hasSucceeded) {return;}// 3. 三更新:执行更新业务逻辑order.update();return;
} finally {Lock.unlock();
}

很多文章会将分布式锁、乐观锁单独拿出来作为一种幂等的方案,我觉得这样理解有失偏颇,因为锁本质上只是为了保证程序原子化互斥执行的手段,本身就不是专门用来保证幂等的,在这三步里就是为了保证「二判」、「三更新」这两步的并发安全。举个极端的例子理解,Redis 锁确实在一定程度上保证了并发安全情况下的幂等,两个相同的资源请求同时进来的时候,只有一个请求能够竞争到锁,另一个请求直接获取不到锁就不阻塞返回了;但假设这两个请求错开进入系统,此时不存在锁的竞争,这个时候两个请求就都能执行了,如果是创建订单就创建了多笔订单,仍然没有达到幂等。所以将锁作为「高并发」场景下保证幂等的一种手段,但不是实现幂等的通用范式。

一锁

锁主要是为了保证并发场景下「二判」、「三更新」这两步的并发安全,因为不锁的话有可能多个线程都没有用最新值去判断,如果评估到业务场景下并发确实很低,其实这一步也可以省略。锁的话就有多种实现方案了,悲观锁或者乐观锁,但是一定要是互斥锁。

  1. 分布式悲观锁

分布式锁的实现方式也比较多 Redis、Zk、MySQL,但业界主流的实现方式还是 Redis, 主要还是考虑到 Redis 的高性能、AP 高可用,通过非阻塞的方式实现并发校验。

  1. 数据库悲观锁

开启事务并对数据库中的记录加上排他行锁,其他事务必须等待本次事务提交后才能执行,同时需要记得行锁都是基于索引的,如果不加索引可能会导致锁表的不良后果。伪代码如下:

// 1. 开启事务
begin;
// 2. 查询出商品信息并加行锁
select quantity from items where id = 1 for update;
// 3. 修改商品信息
update items set quantity = 2 where id = 1;
// 4. 提交事务
commit;

数据库悲观锁效率低,串行执行,更新失败概率低,适用于并发写入比较频繁的场景。

  1. 数据库乐观锁

并未显示加锁,通过版本号 version 实现 CAS 机制

// 1. 查询原版本号
select quantity, origin_version from item where id = 1;
// 2. CAS 更新
update item set quantity = 2 and version = origin_version + 1 where id = 1 and version = origin_version

数据库乐观锁在读多写少的场景效率高,一旦放到并发冲突比较多的场景下或者锁的粒度没有掌控好,更新失败的概率就会变高

二判

判断这一步主要是进行幂等判断,Token机制、业务字段、请求序列号(前三种其实都是生成业务幂等号的方式)、唯一索引、状态机、都能实现类似的功能

  1. 幂等号 - Token 机制

常用在防重复下单的场景,用户每次访问页面时都先向后端请求一个 token,之后在本页面的操作都需要将此 token 带过来,页面不刷新 token 也不变。

Token 生成:

String token = UUID.randomUUID().toString();
jedis.setex(token, 60 * 60, "1");
jedis.close();

Token 校验,del 会返回被删除 key 的数量,返回1代表删除了1条,一个操作来保证原子

Transaction tx = jedis.multi();
if (tx.del(token) == 1) {// 成功
} else {// 失败
}
jedis.close();
  1. 幂等号 - 业务字段

常用在重复消费的场景下,上下游约定好一个幂等字段的生成方式,通过特定业务字段的拼接传递给下游,供下游做判断

  1. 幂等号 - 请求序列号

通过操作流水来做幂等,常用在金融系统中,或者有流水记录的业务系统中
在这里插入图片描述

  1. 唯一索引

这一步是最终兜底方案,通过数据库的唯一索引充当最后的防线

CREATE TABLE orders (id BIGINT PRIMARY KEY,order_no VARCHAR(32) UNIQUE,...
);

异常处理示例

try {orderDao.insert(order);
} catch (DuplicateKeyException e) {log.warn("重复订单:{}", order.getOrderNo());return Result.error("订单已存在");
}
  1. 状态机(业务流程控制)

碰到这种多状态的实体一定要设计好状态流转

Order order = orderService.queryOrder();
if (order.status != init) {return;
} else {order.setStatus(finished);
}

在这里插入图片描述

三更新

这没有什么好说的,就是在第二步判断为首次操作的情况下,更新数据库状态

方案选型对比

  1. 并发较低的情况下不必要上锁
  2. 请求序列号可靠性最高,但是实现复杂度也高;状态机流转适合多状态业务,实现复杂度适中。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【已解决】windows gitbash 出现CondaError: Run ‘conda init‘ before ‘conda activate‘

在 Git Bash 中执行: source /c/Users/你的用户名/miniconda3/etc/profile.d/conda.sh # 注意填入你自己的路径 conda init bash关闭并重新打开 Git Bash 终端。测试激活环境: conda activate your_env_name注意事项 要把上述命令中的 你的用户名 替…

软件包管理系统的架构与生态机制

文章目录 前言一、总结二、如何上传自己的软件包 前言 在日常软件开发中,我们经常使用诸如apt install, pip install, npm install之类的命令,但有一个问题是,这些下载命令是从哪里下载的这些软件包,以及我们是否能上传自己的代码…

Java线程池管理最佳实践(设计模式)

引言 在多线程编程中,线程池是一种非常重要的资源管理工具。合理使用线程池可以显著提高系统性能,避免频繁创建和销毁线程带来的开销。今天,我将为大家深入分析一个实用的ThreadPoolManager实现,它来自com.kingdee.eas.util包&am…

4.8.2 利用Spark SQL计算总分与平均分

在本次实战中,我们的目标是利用Spark SQL计算学生的总分与平均分。首先,我们准备了包含学生成绩的数据文件,并将其上传至HDFS。接着,通过Spark的交互式编程环境,我们读取了成绩文件并将其转换为结构化的DataFrame。然后…

HTML 文件路径完全指南:相对路径、绝对路径解析与引用技巧

一、为什么必须学会文件路径?—— 网页引用资源的 “地址规则” 在 HTML 中,引用图片、CSS、JS 等外部文件时,必须通过文件路径告诉浏览器资源的位置。路径错误会导致资源无法加载(页面出现 broken image 图标或样式丢失&#xf…

keepalived两台设备同时出现VIP问题

目录 问题背景: 日志分析如下: 原因和解决方案总结: 问题背景: keepalived-master和keepalived-slave同时出现了VIP,出现了非对称路由和双主现象 日志分析如下: master能够接受到来自slave的通告消息…

【开源解析】基于PyQt5+Folium的谷歌地图应用开发:从入门到实战

🌐【开源解析】基于PyQt5Folium的谷歌地图应用开发:从入门到实战 🌈 个人主页:创客白泽 - CSDN博客 🔥 系列专栏:🐍《Python开源项目实战》 💡 热爱不止于代码,热情源自每…

篇章五 数据结构——链表(一)

目录 1.ArrayList的缺陷 2. 链表 2.1 链表的概念及结构 2.2 链表结构 1. 单向或者双向 2.带头或者不带头 3.循环或者非循环 2.3 链表的实现 1.完整代码 2.图解 3.显示方法 4.链表大小 5. 链表是否存在 key 值 6.头插法 7.尾插法 8.中间插入 9.删除key值节点 10.…

数据库相关面试

数据库相关面试 Mysql MySQL中的事务隔离级别及其特点 参考:事务的隔离级别:可重复读 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据 已提交读(Read Committed):只能读取到…

基于Scrapy的天猫商品数据爬取与分析实战(含API签名破解与可视化)

基于Scrapy的天猫商品数据爬取与分析实战(含API签名破解与可视化) 本文以华为Mate 60 Pro为例,详细介绍如何使用Scrapy框架爬取天猫商品数据,涵盖API签名破解、反爬应对、数据存储及可视化全流程,适合爬虫进阶学习者实…

【C++进阶篇】哈希表的模拟实现(赋源码)

这里写目录标题 前言一. 开放地址法实现哈希表1.1 闭散列结构定义1.2 构造函数1.3 插入(线性探测)1.3.1 传统写法1.3.2 现代写法 1.4 查找1.5 删除 二. 链地址法实现哈希表(哈希桶)2.1 开散列结构定义2.2 构造函数2.3 插入2.4 查找…

07-后端Web实战(部门管理)

5. 修改部门 对于任何业务的修改功能来说,一般都会分为两步进行:查询回显、修改数据。 5.1 查询回显 5.1.1 需求 当我们点击 "编辑" 的时候,需要根据ID查询部门数据,然后用于页面回显展示。 5.1.2 接口描述 参照参照…

深度解析项目集方向或目标根本性转变的应对策略 —— 项目集管理实战指南

在复杂多变的商业环境中,项目集管理面临着重重挑战,而项目集方向或目标的根本性转变无疑是其中最具冲击力的问题之一。本文将深度剖析这一难题,为项目集管理从业者提供实用、新颖且富有价值的应对策略,助力大家在项目集管理的复杂…

JAVA面试复习知识点

面试中遇到的题目,记录复习(持续更新) Java基础 1.String的最大长度 https://www.cnblogs.com/wupeixuan/p/12187756.html 2.集合 Collection接口的实现: List接口:ArraryList、LinkedList、Vector Set接口&#xff1a…

【烧脑算法】定长滑动窗口:算法题中的“窗口”智慧

目录 引言 定长滑动窗口习题剖析 3439. 重新安排会议得到最多空余时间 I 2134. 最少交换次数来组合所有的 1 II 1297. 子串的最大出现次数 2653. 滑动子数组的美丽值 567. 字符串的排列 438. 找到字符串中所有字母异位词 30. 串联所有单词的子串 220. 存在重复元素 II…

JWT安全:接收无签名令牌.【签名算法设置为none绕过验证】

JWT安全:假密钥【签名随便写实现越权绕过.】 JSON Web 令牌 (JWT)是一种在系统之间发送加密签名 JSON 数据的标准化格式。理论上,它们可以包含任何类型的数据,但最常用于在身份验证、会话处理和访问控制机制中发送有关用户的信息(“声明”)。…

XGBoost与SHAP深度解析:从算法原理到实战价值

在机器学习领域,XGBoost以其卓越的性能长期占据Kaggle竞赛和工业界的主流地位,而SHAP(SHapley Additive exPlanations)则成为模型可解释性的标杆工具。本文将深度解析两者的技术内核,并通过实战案例揭示其结合应用的实…

Java SE Cloneable接口和深/浅拷贝

Java为我们提供了各种各样功能的接口,Clonable接口就是其中之一。 它通常配合Object类的 clone方法使用。这个方法可以为我们创建一个对象的拷贝,即复制一个对象。在进入本文的主要内容之前,先来对访问限定符 protected进行一个解剖。 1.再…

Python学习(3) ----- Python的函数定义及其使用

Python 中函数是组织好的、可重复使用的代码块,用于实现单一或相关联的功能。下面是函数定义和使用的完整说明: 📌 一、函数定义语法 def 函数名(参数1, 参数2默认值, *args, **kwargs):"""函数说明文档"""函…

vue2使用el-tree实现两棵树间节点的拖拽复制

原文链接&#xff1a;两棵el-tree的节点跨树拖拽实现 参照这篇文章&#xff0c;把它做成组件&#xff0c;新增左侧树&#xff08;可拖出&#xff09;被拖节点变灰提示&#xff1b; 拖拽中&#xff1a; 拖拽后&#xff1a; TreeDragComponent.vue <template><!-- …