114 接口中幂等性的保证

2024-04-19 06:04
文章标签 接口 114 保证

本文主要是介绍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;}}

这篇关于114 接口中幂等性的保证的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/916771

相关文章

Java后端接口中提取请求头中的Cookie和Token的方法

《Java后端接口中提取请求头中的Cookie和Token的方法》在现代Web开发中,HTTP请求头(Header)是客户端与服务器之间传递信息的重要方式之一,本文将详细介绍如何在Java后端(以Sp... 目录引言1. 背景1.1 什么是 HTTP 请求头?1.2 为什么需要提取请求头?2. 使用 Spr

Java 后端接口入参 - 联合前端VUE 使用AES完成入参出参加密解密

加密效果: 解密后的数据就是正常数据: 后端:使用的是spring-cloud框架,在gateway模块进行操作 <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.0-jre</version></dependency> 编写一个AES加密

java线程深度解析(一)——java new 接口?匿名内部类给你答案

http://blog.csdn.net/daybreak1209/article/details/51305477 一、内部类 1、内部类初识 一般,一个类里主要包含类的方法和属性,但在Java中还提出在类中继续定义类(内部类)的概念。 内部类的定义:类的内部定义类 先来看一个实例 [html]  view plain copy pu

模拟实现vector中的常见接口

insert void insert(iterator pos, const T& x){if (_finish == _endofstorage){int n = pos - _start;size_t newcapacity = capacity() == 0 ? 2 : capacity() * 2;reserve(newcapacity);pos = _start + n;//防止迭代

京东物流查询|开发者调用API接口实现

快递聚合查询的优势 1、高效整合多种快递信息。2、实时动态更新。3、自动化管理流程。 聚合国内外1500家快递公司的物流信息查询服务,使用API接口查询京东物流的便捷步骤,首先选择专业的数据平台的快递API接口:物流快递查询API接口-单号查询API - 探数数据 以下示例是参考的示例代码: import requestsurl = "http://api.tanshuapi.com/a

如何保证android程序进程不到万不得已的情况下,不会被结束

最近,做一个调用系统自带相机的那么一个功能,遇到的坑,在此记录一下。 设备:红米note4 问题起因 因为自定义的相机,很难满足客户的所有需要,比如:自拍杆的支持,优化方面等等。这些方面自定义的相机都不比系统自带的好,因为有些系统都是商家定制的,难免会出现一个奇葩的问题。比如:你在这款手机上运行,无任何问题,然而你换一款手机后,问题就出现了。 比如:小米的红米系列,你启用系统自带拍照功能后

股票数据接口-陈科肇

陈科肇 新浪财经 sz-深圳sh-上海历史分价表:http://market.finance.sina.com.cn/pricehis.php?symbol=sz000506&startdate=2016-12-27&enddate=2016-12-27历史成交明细(当日成交明细):http://vip.stock.finance.sina.com.cn/quotes_service/v

实例demo理解面向接口思想

浅显的理解面向接口编程 Android开发的语言是java,至少目前是,所以理解面向接口的思想是有必要的。下面通过一个简单的例子来理解。具体的概括我也不知道怎么说。 例子: 现在我们要开发一个应用,模拟移动存储设备的读写,即计算机与U盘、MP3、移动硬盘等设备进行数据交换。已知要实现U盘、MP3播放器、移动硬盘三种移动存储设备,要求计算机能同这三种设备进行数据交换,并且以后可能会有新的第三方的

对接话费充值API接口的开发步骤以及各种优势

对接话费充值API接口通常涉及以下步骤: 1.选择API提供商: 研究并选择一个可靠的话费充值API提供商。考虑因素包括覆盖范围、费率、交易限额、客户支持和用户评价。 2.注册和获取API密钥: 在选定的API提供商平台上注册账户,并获取API密钥或访问令牌,这是调用API时进行身份验证的必要信息。 3.阅读API文档: 仔细阅读API文档,了解如何构建请求、需要哪些参数、API的

java类中定义接口的有哪些好处

第一步:首先是是定义一个类,同时里面定义接口 public class Util { public interface Worker { void work(int a); } } 第二步:定义一个类去实现第一步类中定义的接口 public class Demo implements Worker { @Override public void work(int a) { System