114 接口中幂等性的保证

news2024/11/25 0:25:23

前言

同样是 面试问题 

如何确保接口的 幂等性 

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

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

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

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

对于 删除元素, 就是检查元素存不存在, 存在 则删除, 不存在 返回相关状态吗, 或者直接成功都 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();
        // lock
        if (userRunningStore.contains(name)) {
            throw new RuntimeException(String.format("有其他用户在新增用户 %s, 请刷新后重试", name));
        }
        userRunningStore.add(name);
        // unlock

        Object result = point.proceed();
        return result;
    }

}

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

@PutMapping("/user")
@ConcurrentLatch
public AuthUser add(AuthUser user) {
    // physic verify
    if (user.getAge() > 0 && user.getAge() < 111) {
        throw new RuntimeException("用户的 age 必须在合法的区间");
    }
    // logistic verify
    Map<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 insert
    existsUser = 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 limit
    if (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";

        // lock
        if (!IdempotentController.tokenStore.contains(token)) {
            throw new RuntimeException("服务器异常, 请刷新后重试");
        }
        IdempotentController.tokenStore.remove(token);
        // unlock

        Object 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 {

    @Resource
    private JdbcTemplate jdbcTemplate;

    @PostMapping("/user")
    @ConcurrentLatch
    public AuthUser update(AuthUser user) {
        // physic verify
        if (user.getAge() > 0 && user.getAge() < 111) {
            throw new RuntimeException("用户的 age 必须在合法的区间");
        }
        // logistic verify
        Map<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 insert
        String 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.coloradmin.cn/o/1606338.html

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

相关文章

IDEA中Docker相关操作的使用教程

一、引言 Docker作为当前最流行的容器化技术&#xff0c;极大地简化了应用的部署和管理。而IntelliJ IDEA作为一款强大的集成开发环境&#xff0c;也提供了对Docker的集成支持。本文将介绍如何在IDEA中配置和使用Docker&#xff0c;包括远程访问配置、服务连接、Dockerfile编写…

【Linux冯诺依曼体系结构】

目录 1.冯诺依曼体系结构原理 1.冯诺依曼体系结构 我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 截至目前&#xff0c;我们所认识的计算机&#xff0c;都是有一个个的硬件组件组成 输入单元&#…

HTML学习笔记:(一)基础方法

Html格式 里面文件使用平台为&#xff1a;w3school 1、基础功能&#xff1a; <html><head> <title>这是我的第一个html页面,会显示在浏览器的标题栏中</title> </head> <!--修改背景颜色 --> <body bgcolor"yellow"> …

如何合理利用多个中国大陆小带宽服务器?

我们知道在中国大陆带宽单价非常昂贵&#xff0c;一个1Mbps 带宽的机子一年就得卖好几百人民币&#xff0c;这是不值当的&#xff0c;当然我们可以去低价漂阿里云、腾讯云的轻量服务器&#xff0c;99包年&#xff0c;但是带宽太小很难崩。 所以&#xff0c;我们必须构建一个能够…

钉钉直播回放怎么下载到本地

钉钉直播回放如何下载到本地,本文就给大家解密如何下载到本地 工具我已经给大家打包好了 钉钉直播回放下载软件链接&#xff1a;https://pan.baidu.com/s/1_4NZLfENDxswI2ANsQVvpw?pwd1234 提取码&#xff1a;1234 --来自百度网盘超级会员V10的分享 1.首先解压好我给大家…

使用脚本启动和关闭微服务

使用脚本启动和关闭微服务 一、前言二、启动1、处理每个服务2、编写启动脚本3、其他启动脚本&#xff08;无效&#xff0c;有兴趣可以看看&#xff09;4、启动 三、关闭1、测试拿服务进程id的命令是否正确2、编写关闭脚本3、关闭 一、前言 假如在服务器中部署微服务不使用 doc…

ElasticSearch:基础操作

一、ES的概念及使用场景 ElasticSearch是一个分布式&#xff0c;高性能、高可用、可伸缩、RESTful 风格的搜索和数据分析引擎。通常作为Elastic Stack的核心来使用 我们通过将ES 和 mysql对比来更好的理解 ES&#xff0c;ES和mysql相关的基本概念的对比表格如下&#xff1a; …

从Linux角度具体理解程序翻译过程-----预处理、编译、汇编、链接

前言&#xff1a; 在C语言中&#xff0c;我们知道程序从我们所写的代码到可执行执行的过程中经历了以下过程 1.预处理 2.编译 3.汇编 4.链接 可以通过下图来理解 翻译过程 1.预处理 该过程主要进行以下操作&#xff1a; (1)头文件的包含 (2)define定义符号的替换&#xff…

稀碎从零算法笔记Day52-LeetCode:从双倍数组中还原原数组

题型&#xff1a;数组、贪心 链接&#xff1a;2007. 从双倍数组中还原原数组 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 一个整数数组 original 可以转变成一个 双倍 数组 changed &#xff0c;转变方式为将 original 中每个元素 值乘以 …

EFK架构部署

7.17版本 准备工作 配置域名 cat >> /etc/hosts <<EOF 192.168.199.149 elk149daidaiedu.com 192.168.199.150 elk150daidaiedu.com 192.168.199.155 elk155daidaiedu.com EOF 修改主机名 hostnamectl set-hostname elk155.daidaiedu.com 免密登录 ssh-keyge…

# 从浅入深 学习 SpringCloud 微服务架构(一)基础知识

从浅入深 学习 SpringCloud 微服务架构&#xff08;一&#xff09;基础知识 1、系统架构演变&#xff1a; 1&#xff09;单体应用架构。如电商项目。 用户管理、商品管理、订单管理&#xff0c;在一个模块里。 优点&#xff1a;开发简单&#xff0c;快速&#xff0c;适用于…

VScode远程连接虚拟机提示: 无法建立连接:XHR failed.问题解决方案

一问题描述 在vscode下载插件Remote-SSH远程连接虚拟机时提示无法建立连接 二.最大嫌疑原因&#xff1a; 我也是在网上找了许久&#xff0c;发现就是网络原因&#xff0c;具体不知&#xff0c;明明访问别的网页没问题&#xff0c;就是连不上&#xff0c;然后发现下载vscode的…

前端CSS基础4(像素,颜色,字体属性大小复合属性)

前端CSS基础4&#xff08;像素&#xff0c;颜色&#xff0c;字体属性大小复合属性&#xff09; CSS代码编写位置CSS像素CSS颜色CSS常用字体属性和大小字体的复合属性 CSS代码编写位置 在HTML文件的头部使用 <head><style>/* 在这里编写CSS代码 */</style> …

Meta Llama 3强势来袭:迄今最强开源大模型,性能媲美GPT-4

前言 Meta的最新语言模型Llama 3已经发布&#xff0c;标志着在大型语言模型&#xff08;LLM&#xff09;领域的一次重大突破&#xff0c;其性能在行业内与GPT-4相媲美。此次更新不仅提升了模型的处理能力和精确性&#xff0c;还将开源模型的性能推向了一个新的高度。 Huggingf…

从0开始学人工智能测试节选:Spark -- 结构化数据领域中测试人员的万金油技术(二)

Dataframe dataframe 是spark中参考pandas设计出的一套高级API&#xff0c;用户可以像操作pandas一样方便的操作结构化数据。毕竟纯的RDD操作是十分原始且麻烦的。而dataframe的出现可以让熟悉pandas的从业人员能用非常少的成本完成分布式的数据分析工作&#xff0c; 毕竟跟数据…

数仓建模—数仓架构发展史

数仓建模—数仓架构发展史 时代的变迁&#xff0c;生死的轮回&#xff0c;历史长河滔滔&#xff0c;没有什么是永恒的&#xff0c;只有变化才是不变的&#xff0c;技术亦是如此&#xff0c;当你选择互联网的那一刻&#xff0c;你就相当于乘坐了一个滚滚向前的时代列车&#xf…

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

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

Ubuntu20.04 ISAAC SIM仿真下载使用流程(4.16笔记补充)

机器&#xff1a;华硕天选X2024 显卡&#xff1a;4060Ti ubuntu20.04 安装显卡驱动版本&#xff1a;525.85.05 参考&#xff1a; What Is Isaac Sim? — Omniverse IsaacSim latest documentationIsaac sim Cache 2023.2.3 did not work_isaac cache stopped-CSDN博客 Is…

2024蓝桥杯每日一题(最短路径)

备战2024年蓝桥杯 -- 每日一题 Python大学A组 试题一&#xff1a;奶牛回家 试题二&#xff1a;Dijkstra求最短路 II 试题三&#xff1a;spfa求最短路 试题四&#xff1a;作物杂交 试题一&#xff1a;奶牛回家 【题目描述】 晚餐时间马上就到了&#x…

【JavaEE多线程】Thread类及其常见方法(上)

系列文章目录 &#x1f308;座右铭&#x1f308;&#xff1a;人的一生这么长、你凭什么用短短的几年去衡量自己的一生&#xff01; &#x1f495;个人主页:清灵白羽 漾情天殇_计算机底层原理,深度解析C,自顶向下看Java-CSDN博客 ❤️相关文章❤️&#xff1a;清灵白羽 漾情天…