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…

基于windowns下的TCP网络通信编程

Tcp通信的三次握手和四次挥手 TCP的三次握手和四次挥手是TCP连接的建立和断开过程&#xff0c;确保了可靠的数据传输和连接状态的正确管理。 TCP的三次握手&#xff08;TCP 链接建立&#xff09;&#xff1a; 1. 客户端发送 SYN &#xff1a; 客户端向服务器发送一个 SYN 报文…

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

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

TCP为什么需要3次握手?

一、3次握手过程 客户端向服务端发送一个表示建立连接的SYN报文段&#xff0c;服务端从IP数据报中提取出TCP SYN报文段&#xff0c;为该TCP连接分配需要的缓存和变量&#xff0c;并向客户端发送表示允许连接的报文段ACK。客户端在收到ACK报文段之后&#xff0c;也要给该连接分配…

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

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

【docker 】docker-compose 部署mongoDB

在notepad中将格式改为UNIX &#xff08;编辑》文档格式转化》转为Unix&#xff09;&#xff0c;编码改为UTF-8 &#xff08; 编码》转为UTF-8&#xff09;&#xff0c;改好后如图 新建启动脚本 mongo.sh #!/bin/bash # 挂载路径 DATA_DIR/opt/docker-data/mongodb/data LOG_…

timesten 安装

1. 安装 从oracle的网站&#xff08;http://www.oracle.com/technology/global/cn/software/products/timesten/index.html&#xff09;下载&#xff0c;timesten的安装文件。 将下载文件解压后&#xff0c;出现linux86文件夹&#xff0c;执行setup.sh&#xff0c;步骤如下…

死磕GMSSL通信-java/Netty系列(三)

死磕GMSSL通信-java/Netty系列&#xff08;三&#xff09; 接着上次的博客继续完善&#xff0c;上次其实只是客户端的改造&#xff0c;这次把服务端的也补上&#xff0c;netty集成GMSSL实现GMServer 1、netty_tcnative c代码改造&#xff0c;这个是客户端和服务端都需要都该的…

数据挖掘与机器学习

一. 机器学习的种类 1. 有监督的机器学习 : 分类 : KNN 最近邻 逻辑回归 - 朴素贝叶斯估计 SVM 线性 或 非线性 优化模型 决策树模型 - 随机森林 - 其它集成模型 lightGBM - XGBOOST 回归: 线性 …

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

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

基于SpringBoot + Vue 的电影售票及影院管理系统(前后端分离)

后端&#xff1a;Spring Boot Mybatis 实现功能&#xff1a; 售票子系统&#xff1a;用户浏览电影信息&#xff0c;电影分类查看&#xff0c;搜索查看&#xff0c;购票操作&#xff08;未实现支付沙箱&#xff09;&#xff0c;超时取消订单等 管理子系统&#xff1a;管理员…

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

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

react中子父组件互相传值

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

面试话术1

自我介绍 面试官您好&#xff01;我叫王鹏宇&#xff0c;本科是在西南石油大学读的&#xff0c;是一所双一流大学。我本人对网络安全领域比较有兴趣的&#xff0c;并且想对国家网络安全建设做出贡献。我去年也参加了护网&#xff0c;在那边主要就是负责流量研判&#xff0c;然…

机器学习在基因组学中的应用

机器学习在基因组学中的应用 李升伟1 茅 矛1 陈 竺2 &#xff08;1.特趣生物科技有限公司&#xff0c;广东省深圳市&#xff1b;2.上海交通大学医学院附属瑞金医院&#xff0c;上海市&#xff09; 机器学习在基因组学中的应用已经变得日益重要和普遍&#xff0c;其核心价…

elementui中文官网

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

android gradle引入其他gradle文件

在Android项目中&#xff0c;如果你想在一个Gradle文件中引入另一个Gradle文件&#xff0c;你可以使用apply from语句。以下是一个简单的例子&#xff1a; 假设你有一个通用的Gradle配置脚本common.gradle&#xff0c;它位于项目的根目录下。 common.gradle 文件内容示例&…

一个 .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…

搭建Bootstrap5+Webpack项目

我是个前端菜鸡&#xff0c;最近准备学习一点前端知识&#xff0c;先从Bootstrap5开始&#xff0c;毕竟早期Bootstrap还是比较火的。推出的Bootstrap5不再和jQery强制绑定&#xff0c;这里直接按照官方文档上来操作&#xff0c;打包工具我们选择Webpack。 一 前期准备 前期准备…

[MySQL数据库] 索引与事务

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