114 接口中幂等性的保证

前言

同样是 面试问题 

如何确保接口的 幂等性 

幂等是一个 较为抽象的概念, 多次重复访问, 不会导致业务逻辑的异常 

这里从增删改查, 几个方面列一下 

一般来说, 我们核心需要关注的就是 新增 和 更新

对于 增加元素, 首先针对唯一约束进行校验, 然后再处理新增的相关业务, 严格一点需要 加锁, 分布式并发控制 

对于 删除元素, 就是检查元素存不存在, 存在 则删除, 不存在 返回相关状态吗, 或者直接成功都 OK

元素的新增

基于持久化的数据库的机制

比如 mysql 这边目标表, 增加唯一索引, 或者 主键

比如, 我们这里 限定在 用户表 中 用户名 不能重复, 这个只有特定的业务场景中可以这么处理 

CREATE TABLE `auth_user` (`id` int(11) NOT NULL,`name` varchar(256) DEFAULT NULL,`age` int(11) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `name` (`name`) USING BTREE COMMENT 'name'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

然后 服务器这边 就不用做 过多的控制, 核心业务部分直接 ”insert into” 都可以 

由 mysql 这边本身的机制 来确保 用户名 的不能重复, 防止 用户多次提交 造成的业务问题

分布式并发过滤控制 + 数据库的悲观锁

我们这里展现一下 完整的处理流程, 主要是包含了 外层的并发过滤控制, 数据库校验控制, 数据库加锁+插入 控制

这里分布式并发控制这里模拟实现, 是 userRunningStore 部分

1. 并发过滤控制这边处理如下, 基于 spring 的 注解 + aop

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConcurrentLatch {}

并发过滤控制的处理如下

@Component
@Aspect
public class ConcurrentLatchAop {@Pointcut("@annotation(ConcurrentLatch)")public void concurrentLatchAop() {}Set<String> userRunningStore = new LinkedHashSet<>();@Around("concurrentLatchAop()")public Object doProcess(ProceedingJoinPoint point) throws Throwable {Object[] args = point.getArgs();AuthUser user = (AuthUser) args[0];String name = user.getName();// lockif (userRunningStore.contains(name)) {throw new RuntimeException(String.format("有其他用户在新增用户 %s, 请刷新后重试", name));}userRunningStore.add(name);// unlockObject result = point.proceed();return result;}}

数据库校验控制, 数据库加锁+插入控制 如下 

@PutMapping("/user")
@ConcurrentLatch
public AuthUser add(AuthUser user) {// physic verifyif (user.getAge() > 0 && user.getAge() < 111) {throw new RuntimeException("用户的 age 必须在合法的区间");}// logistic verifyMap<String, Object> existsUser = JdbcTemplateUtils.queryOne(jdbcTemplate, String.format(" select * from auth_user where name = '%s'; ", user.getName()));if (existsUser != null) {throw new RuntimeException("该用户已经存在, 用户名称不能重复");}// do other biz// lock then insertexistsUser = JdbcTemplateUtils.queryOne(jdbcTemplate, String.format(" select * from auth_user where name = '%s' for update; ", user.getName()));if (existsUser != null) {throw new RuntimeException("该用户已经存在, 用户名称不能重复");}jdbcTemplate.execute(String.format("INSERT INTO `auth_user`(`name`, `age`) VALUES ('%s', %s);", user.getName(), user.getAge()));return user;
}

token分布式并发控制 + 数据库的悲观锁

这个就主要是 整体的交互机制调整, 增加了一层 token 的获取 和 验证

token 的分派这边如下, 做限流, 生成 token 的相关处理 

public static Map<String, AtomicInteger> interf2Counter = new LinkedHashMap<>();
public static Set<String> tokenStore = new LinkedHashSet<>();// pre install all interfs
static {interf2Counter.put("IdempotentController.add", new AtomicInteger());
}@GetMapping("/requestToken")
public String requestToken(String interf) {AtomicInteger counter = interf2Counter.get(interf);int incred = counter.getAndIncrement();// rate limitif (incred > 20) {counter.getAndDecrement();throw new RuntimeException(" 服务器繁忙, 请稍后重试 ");}String token = UUID.randomUUID().toString();String compositeToken = interf + token;tokenStore.add(compositeToken);return token;
}

并发控制这边处理如下 

/*** ConcurrentLatchAop** @author Jerry.X.He* @version 1.0* @date 2023/9/21 10:17*/
@Component
@Aspect
public class ConcurrentLatchAop {@Pointcut("@annotation(ConcurrentLatch)")public void concurrentLatchAop() {}@Around("concurrentLatchAop()")public Object doProcess(ProceedingJoinPoint point) throws Throwable {Object[] args = point.getArgs();String interf = "get interf from request";String token = "get token from request";// lockif (!IdempotentController.tokenStore.contains(token)) {throw new RuntimeException("服务器异常, 请刷新后重试");}IdempotentController.tokenStore.remove(token);// unlockObject result = point.proceed();AtomicInteger counter = IdempotentController.interf2Counter.get(interf);counter.getAndDecrement();return result;}}

元素的更新

以上 三种处理方式 在元素的更新中同样可以使用

元素的更新 数据库的更新控制这边可以使用 基于数据库的乐观锁 

数据库的乐观锁更新

并发控制这边 和上面类似, 我们这里着重关注 数据库的更新这边 

数据库的更新这边, 主要是增加一个版本号的字段, 然后 更新的时候 在原有的 id 条件之外, 再增加一个 version 控制的字段 

根据 mysql 这边更新, 会增加行排他锁, 具体的处理如下 

/*** IdempotentController** @author Jerry.X.He* @version 1.0* @date 2023/9/21 9:58*/
@RestController
@RequestMapping("/idempotent")
public class IdempotentController {@Resourceprivate JdbcTemplate jdbcTemplate;@PostMapping("/user")@ConcurrentLatchpublic AuthUser update(AuthUser user) {// physic verifyif (user.getAge() > 0 && user.getAge() < 111) {throw new RuntimeException("用户的 age 必须在合法的区间");}// logistic verifyMap<String, Object> existsUser = JdbcTemplateUtils.queryOne(jdbcTemplate, String.format(" select * from auth_user where name = '%s'; ", user.getName()));if (existsUser == null) {throw new RuntimeException("该用户不存在, 请确认输入");}// do other biz// lock then insertString id = String.valueOf(existsUser.get("id"));String version = String.valueOf(existsUser.get("version"));int updatedCount = jdbcTemplate.update(String.format("update auth_user set name = '%s', age = %s where id = %s and version = %s;", user.getName(), user.getAge(), id, version));if (updatedCount == 0) {throw new RuntimeException("该用户信息已经发生改变, 请刷新后重试");}return user;}}

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

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

相关文章

Day09 React———— 第九天

ReactRoter 一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候&#xff0c;path 对应的组件会在页面中进行渲染 基础用法 import { createBrowserRouter, RouterProvider } from "react-router-dom"; const router createBrowserRoute…

AI讲师人工智能讲师大模型培训讲师叶梓:突破大型语言模型推理效率的创新方法

大型语言模型&#xff08;LLM&#xff09;在自然语言处理&#xff08;NLP&#xff09;任务中展现出了前所未有的能力&#xff0c;但它们对计算资源的巨大需求限制了其在资源受限环境中的应用。SparQ Attention算法提出了一种创新的方法&#xff0c;通过减少注意力机制中的内存带…

探索Java世界中的七大排序算法(上)

文章目录 排序的概念直接插入排序希尔排序( 缩小增量排序)选择排序堆排序冒泡排序 在计算机科学中&#xff0c;排序算法是一类重要的算法&#xff0c;它们用于将一组元素按照一定的顺序进行排列。在Java编程中&#xff0c;我们经常需要对数组或集合进行排序操作。本文将介绍Jav…

驱动云创建保存自己的环境

驱动云创建保存自己的环境 制作镜像方法一方法二报错 上一篇link介绍了如何在驱动云上部署llama2以及驱动云在训练大模型的方便之处。也说到了可以直接使用驱动云现有的环境&#xff0c;免得自己配置环境。 但是有的时候免不了自己想要安装一些包。 驱动云的环境是这样的&…

电视音频中应用的音频放大器

电视机声音的产生原理是将电视信号转化为声音&#xff0c;然后通过扬声器将声音播放出来。当我们打开电视并选择频道时&#xff0c;电视机首先从天线或有线电视信号中获取声音信号。声音信号经过放大器放大之后&#xff0c;就能够通过扬声器发出声音。电视机声音的产生原理和音…

react中子父组件互相传值

在react中父子组件互相传值&#xff0c;除了使用类似于redux这样状态管理的工具&#xff0c;怎么实现&#xff1f;&#xff1f; 父传子(简单)父:子: 子传父(较麻烦)父&#xff1a;子&#xff1a; 父传子(简单) 父: 子: 子传父(较麻烦) 父&#xff1a; 子&#xff1a;

elementui中文官网

Element - The worlds most popular Vue UI frameworkElement&#xff0c;一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库https://element.eleme.cn/#/zh-CN/

一个 .net 8 + Azure 登录 + Ant Design Blazor 的基本后台框架

一个 .net 8 Azure 登录 Ant Design Blazor 的基本后台框架 主界面使用了 Ant Design Blazor 项目模板搭建 后台技术是 .net 8 Blazor run at server 模式 登录方式使用 Azure 实现了菜单导航和路由 此外实现了读取和修改本地Json文件的功能&#xff0c;不是必须的&#x…

[MySQL数据库] 索引与事务

1. 索引 1.1 概念 索引是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用指针.可以对表中的一列或多列创建索引,并指定索引的类型&#xff0c;各类索引有各自的数据结构实现. 1.2 作用 数据库中的表、数据、索引之间的关系&#xff0c;类似于书架上的图书、书籍…

【力扣】148. 排序链表

148. 排序链表 题目描述 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4] 示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,…

深度学习架构(CNN、RNN、GAN、Transformers、编码器-解码器架构)的友好介绍。

一、说明 本博客旨在对涉及卷积神经网络 &#xff08;CNN&#xff09;、递归神经网络 &#xff08;RNN&#xff09;、生成对抗网络 &#xff08;GAN&#xff09;、转换器和编码器-解码器架构的深度学习架构进行友好介绍。让我们开始吧&#xff01;&#xff01; 二、卷积神经网络…

【观察】容器化部署“再简化”,云原生体验“再升级”

自2013年云原生概念被提出以来&#xff0c;云原生技术和架构在过去十多年得到了迅速的发展&#xff0c;并对数字基础设施、应用架构和应用构建模式带来了深刻的变革。根据IDC预测&#xff0c;到2024年&#xff0c;新增的生产级云原生应用在新应用的占比将从2020年的10%增加到60…

Java学习-详述main方法、可变参数、数组的工具类、二维数组

详述main方法 【1】main方法&#xff1a;程序的入口&#xff0c;在同一个类中&#xff0c;如果有多个方法&#xff0c;那么虚拟机就会识别main方法&#xff0c;从这个方法作为程序的入口 【2】main方法格式严格要求&#xff1a; public static void main(String[] args){} p…

线性代数---行列式的性质

1. 行列式的行与列(按原顺序)互换

SpringCloud +UniApp技术开发saas模式的智慧工地云平台源码,支持可视化大屏端、手机端、平板端、PC端

基于微服务架构JavaSpring Cloud UniApp MySql技术开发saas模式的一套智慧工地云平台源码&#xff0c;支持多端展示&#xff1a;可视化大屏端、手机端、平板端、PC端。 智慧工地平台支持项目级、公司级、集团级多级权限划分&#xff0c;可根据企业的组织架构进行项目权限、功能…

编程入门(四)【计算机网络基础(由一根网线连接两个电脑开始)】

读者大大们好呀&#xff01;&#xff01;!☀️☀️☀️ &#x1f525; 欢迎来到我的博客 &#x1f440;期待大大的关注哦❗️❗️❗️ &#x1f680;欢迎收看我的主页文章➡️寻至善的主页 文章目录 前言两个电脑如何互连呢&#xff1f;集线器、交换机与路由器总结 前言 当你有…

【opencv】dnn示例-speech_recognition.cpp 使用DNN模块结合音频信号处理技术实现的英文语音识别...

模型下载地址&#xff1a; https://drive.google.com/drive/folders/1wLtxyao4ItAg8tt4Sb63zt6qXzhcQoR6 终端输出&#xff1a;&#xff08;audio6.mp3 、audio10.mp3&#xff09; [ERROR:00.002] global cap_ffmpeg_impl.hpp:1112 open VIDEOIO/FFMPEG: unsupported parameter…

华为手机p70即将上市,国内手机市场或迎来新局面?

4月15日&#xff0c;华为官宣手机品牌全新升级&#xff0c;p系列品牌升级为Pura。华为P70系列手机预计将于2024年第一季度末发布&#xff0c;而网友也纷纷表示期待p70在拍照、性能上的全新突破。 网友们对华为P70系列的热情高涨&#xff0c;也印证了国内高端手机市场的潜力巨大…

MySql安装(Linux)

一、清除原来的mysql环境 在前期建议使用root用户来进行操作&#xff0c;使用 su -来切换成root用户&#xff0c;但是如果老是提示认证失败&#xff0c;那么有可能我们的root密码并没有被设置&#xff0c; 我们可以先设置root的密码 sudo passwd root 然后就可以切换了。 …

Mac 利用Homebrew安装JDK

一、安装JDK17 1.安装openjdk17 2.把homebrew安装的openjdk17软链接到系统目录&#xff1a; brew install openjdk17 sudo ln -sfn $(brew --prefix)/opt/openjdk17/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-17.jdk 一、检查是否安装成功 在Termina…