确保接口安全:六大方案有效解决幂等性问题

news2024/11/24 12:23:25

文章目录

  • 六大方案解决接口幂等问题
    • 什么是接口幂等?
    • 天然幂等
    • 不做幂等会怎么样?
  • 解决方案
    • 1)insert前先select
    • 2)使用唯一索引
    • 3)去重表加悲观锁
    • 4)加乐观锁之版本号机制
    • 5)使用 Redisson 分布式锁
    • 6)Token 机制

六大方案解决接口幂等问题

什么是接口幂等?

幂等(idempotency)本身是一个数学概念,常见与抽象代数中,代表一个函数或操作的结果不受其输入或者执行次数的影响,例如,f(n) = 1^n,无论 n 为多少,f(n)的值永远为 1 。

在软件开发领域,幂等对请求执行结果的一个描述,这个描述就是无论执行多少次相同的请求,产生的效果和返回的结果和发出单个请求是一样的。

举个例子🌰:

  • 有时我们在填写某些form表单时,保存按钮不小心快速点了两次,表中竟然产生了两条重复的数据,只是id不一样。
  • 我们在项目中为了解决接口超时问题,通常会引入了重试机制。第一次请求接口超时了,请求方没能及时获取返回结果(此时有可能已经成功了),为了避免返回错误的结果(这种情况不可能直接返回失败吧?),于是会对该请求重试几次,这样也会产生重复的数据。

天然幂等

那有没有有些情况是天然支持幂等的呢?当然有!比如说我要更新一个某记录的状态 status = 1 具体的 sql 为 update table set status = 1 where id = 1 这种情况,无论我执行多少次这条 sql 他的效果是一样的,这就是天然支持幂等的。

不做幂等会怎么样?

比如说用户在付款的时候,同时点击多次付款按钮,后端处理了多次扣款请求,结果导致用户的账户扣了多次钱。妥妥 p0 事故呀!

到这你又会说,前端做个置灰按钮不就行了吗,第一次付款完毕后,那用户或者恶意攻击你服务器的人直接用脚本搞你不走前端,你是防止不了的。

那接下来,我将介绍六大解决接口幂等的方案,速速点赞上车!!!

解决方案

1)insert前先select

通常情况下,在保存数据的接口中,我们为了防止产生重复数据,一般会在insert前,先根据namecode字段select一下数据。如果该数据已存在,则执行update操作,如果不存在,才执行 insert操作。

在这里插入图片描述

该方案可能是我们平时在防止产生重复数据时,使用最多的方案。但是该方案不适用于并发场景,在并发场景中,要配合其他方案一起使用,否则同样会产生重复数据。

2)使用唯一索引

通过在表中加上唯一索引,保证数据的唯一性。如果有重复的数据插入,会抛出DuplicateKeyException异常,程序可以捕获异常并处理。不过,这种方法只适用于插入数据的场景。

create table t_order(
	id int unsigned PRIMARY KEY AUTO_INCREMENT COMMENT "主键",
    code varchar(200) not null COMMENT "流水号",
    user_id  int unsigned COMMENT "用户id",
    amount decimal(10,2) unsigned not null COMMENT "总金额",
    UNIQUE unq_code(code)
) COMMENT="订单表";

不要依靠唯一索引来保证接口幂等,但建议使用唯一索引作为兜底,避免产生脏数据

伪代码如下:

public void idempotent(OrderDO orderDO){
    try {
       // 执行核心业务...
       orderMapper.insert(orderDO);
    }
    catch(DuplicateKeyException e) {
        // 有重复的数据插入
    }
}

3)去重表加悲观锁

去重表本质上也是一种唯一索引方案。去重表是一张专门用于记录请求信息的表,其中某个字段需要建立唯一索引,用于标识请求的唯一性当客户端发出请求时,服务端会将这次请求的一些信息(如订单号、交易流水号等)插入到去重表中,如果插入成功,说明这是第一次请求,可以执行后续的业务逻辑;如果插入失败,说明这是重复请求,可以直接返回或者忽略。

CREATE TABLE deduplication_table (
    id int unsigned PRIMARY KEY AUTO_INCREMENT COMMENT "主键",
    processed_code varchar(200) not null COMMENT "已处理的订单流水号",
    -- 省略其他字段
    UNIQUE unq_processed_code(processed_code)
) COMMENT="去重表";

使用for update加锁每次查询到都是最新的数据

select * from deduplication_table where processed_code = 'xxx' for update

伪代码如下:

public boolean idempotent(OrderDO orderDO){
    // 执行核心业务之前
    DoMain domain = deduplicationMapper.selectForUpdate(orderDO.getProcessedCode);
    if(doamin != null) {
        // 订单已经支付
    }
}

4)加乐观锁之版本号机制

既然悲观锁有性能问题,为了提升接口性能,我们可以使用乐观锁。需要在表中增加一个timestamp或者version字段,这里以version字段为例。

在更新数据之前先查询一下数据:

select id,amount,version from user id=123;

如果数据存在,假设查到的version等于1,再使用idversion字段作为查询条件更新数据:

update user set amount = amount + 100, version = version + 1 where id = 123 and version = 1;

更新数据的同时version+1,然后判断本次update操作的影响行数,如果大于0,则说明本次更新成功,如果等于0,则说明本次更新没有让数据变更。

由于第一次请求version等于1是可以成功的,操作成功后version变成2了。这时如果并发的请求过来,再执行相同的sql:

update user set amount = amount + 100,version = version + 1 where id = 123 and version = 1;

update操作不会真正更新数据,最终sql的执行结果影响行数是0,因为version已经变成2了,where中的version=1肯定无法满足条件。但为了保证接口幂等性,接口可以直接返回成功,因为version值已经修改了,那么前面必定已经成功过一次,后面都是重复的请求。

具体流程如下:在这里插入图片描述

具体步骤:

  1. 先根据id查询用户信息,包含version字段
  2. 根据id和version字段值作为where条件的参数,更新用户信息,同时version+1
  3. 判断操作影响行数,如果影响1行,则说明是一次请求,可以做其他数据操作。
  4. 如果影响0行,说明是重复请求,则直接返回成功。

5)使用 Redisson 分布式锁

基于 MySQL 也可以实现分布式锁,但一般我们不会采用这种方式。

通常情况下,我们一般会选择基于 Redis 或者 ZooKeeper 实现分布式锁,Redis 用的要更多一点。

// 唯一标识
String uniqueId = "orderId123";
// 1. 根据唯一标识生成分布式锁对象
RLock lock = redisson.getLock("lock:" + uniqueId);

try {
    // 2. 尝试获取锁(Watch Dog 自动续期机制) 
    if (lock.tryLock()) {
        // 3. 如果成功获取到锁,说明请求还没有被处理,执行业务逻辑
    } else {
        // 请求已经被处理,直接返回
    }
} finally {
    // 4. 释放锁
    lock.unlock();
}

6)Token 机制

Token 机制的核心思想是为每一次操作生成一个唯一性的凭证 token。这个 token 需要由服务端生成的,因为服务端可以对 token 进行签名和加密,防止篡改和泄露。如果由客户端生成 token,可能会存在安全隐患,比如客户端伪造或重复 token,导致服务端无法识别和校验。

这样的话,就需要两次请求才能完成一次业务操作:

  1. 请求获取服务器端 token,token 需要设置有效时间(可以设置短一点),服务端将该 token 保存起来。

  2. 执行真正的请求,将上一步获取到的 token 放到 header 或者作为请求参数。服务端验证 token 的有效性,如果有效(一般是通过删除 token 的方式来验证,删除成功则有效),执行业务逻辑,并删除 token,防止重复提交;如果无效,拒绝请求,返回提示信息。

// 获取token
public String getToken(Long busId, Long userId){
    String UUID = UUID.randomUUID().toString();
    stringRedisTemplate.opsForValue().set(busId+userId, UUID, 20, TimeUnit.SECONDS)
    return UUID;
}
// 发起业务请求携带token
public void doSomeBusiness(Parameter parameter) {
    Long busId = parameter.getBusId;
    Long userId = UserContext.getUserId();
    // 判断token是否存在
 	Boolean deleted = stringRedisTemplate.delete(busId+userId);
    if(deleted) {
        // 删除成功,代表重复请求不进行操作,直接返回
        return;
    }
    // doSomeBusiness...
}

在这里插入图片描述

具体步骤:

  1. 用户访问页面时,浏览器自动发起获取 token 请求。
  2. 服务端生成 token,保存到 redis 中,然后返回给浏览器。
  3. 用户通过浏览器发起请求时,携带该 token。
  4. 从 redis 中尝试删除 token 如果删除失败,说明是第一次请求,做则后续的数据操作。
  5. 如果删除成功,说明是重复请求,不做任何操作。

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

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

相关文章

银河麒麟系统内存清理

银河麒麟系统内存清理 1、操作步骤2、注意事项 💐The Begin💐点点关注,收藏不迷路💐 当银河麒麟系统运行较长时间,内存中的缓存可能会积累过多,影响系统性能。此时,你可以通过简单的命令来清理这…

问:LINUXWINDOWS线程CPU时间如何排序?

Linux 在Linux上,你可以使用ps命令结合sort命令来查看和排序进程或线程的CPU使用时间。 查看进程的CPU使用时间并按时间排序 使用ps命令的-o选项可以自定义输出格式,-e选项表示显示所有进程,--sort选项用于排序。 ps -e -o pid,tid,comm,…

排序大全(干货)

目录 1. 插入排序步骤: 2.选择排序思路:每次从待排序列中选出一个最小值,然后放在序列的起始位置,直到全部待排数据排完即可。实际上,我们可以一趟选出两个值,一个最大值一个最小值,然后将其放…

【springboot】整合LoadBalancer

目录 问题产生背景解决方案:实现LoadBalancer1. 添加依赖2. 配置文件3. 使用LoadBalancer4. 使用 RestTemplate 进行服务调用5. 测试 问题产生背景 以下是一个购物车项目,通过调用外部接口获取商品信息,并添加到购物车中,这段代码…

如何使用ssm实现中学生课后服务的信息管理与推荐+vue

TOC ssm766中学生课后服务的信息管理与推荐vue 第一章 绪论 1.1 选题背景 目前整个社会发展的速度,严重依赖于互联网,如果没有了互联网的存在,市场可能会一蹶不振,严重影响经济的发展水平,影响人们的生活质量。计算…

查缺补漏----I/O中断处理过程

中断优先级包括响应优先级和处理优先级,响应优先级由硬件线路或查询程序的查询顺序决定,不可动态改变。处理优先级可利用中断屏蔽技术动态调整,以实现多重中断。下面来看他们如何运用在中断处理过程中: 中断控制器位于CPU和外设之…

SpringBoot开发:古典舞在线交流平台的架构与实现

第三章 系统分析 3.1 可行性分析 需要使用大部分精力开发的古典舞在线交流平台为了充分降低开发风险,特意在开发之前进行可行性分析这个验证系统开发是否可行的步骤。本文就会从技术角度,经济角度,还有操作角度等进行综合阐述。 3.1.1技术可行…

排序01 多目标模型

引入 使用机器学习方法对指标做预估,再对预估分数做融合。融合方法:加权和方法给不同指标赋予不同的权重,权重是做A/B test调试得到的。还有更好地融合方法。 多目标模型 排序模型的输入是各种各样的特征,用户特征主要是用户id和…

易趋(EasyTrack)资深顾问唐颖受邀为第四届中国项目经理大会演讲嘉宾

全国项目经理专业人士年度盛会 易趋(EasyTrack)资深顾问唐颖女士受邀为PMO评论主办的全国项目经理专业人士年度盛会——2024第四届中国项目经理大会演讲嘉宾,演讲议题为“隐形翅膀——数字化项目管理助力项目经理鹏程万里”。大会将于10月26-…

古典舞在线互动:SpringBoot平台设计与功能实现

第三章 系统分析 3.1 可行性分析 需要使用大部分精力开发的古典舞在线交流平台为了充分降低开发风险,特意在开发之前进行可行性分析这个验证系统开发是否可行的步骤。本文就会从技术角度,经济角度,还有操作角度等进行综合阐述。 3.1.1技术可行…

Prometheus之Pushgateway使用

Pushgateway属于整个架构图的这一部分 The Pushgateway is an intermediary service which allows you to push metrics from jobs which cannot be scraped. The Prometheus Pushgateway exists to allow ephemeral and batch jobs to expose their metrics to Prometheus. S…

扩散引导语言建模(DGLM):一种可控且高效的AI对齐方法

随着大型语言模型(LLMs)的迅速普及,如何有效地引导它们生成安全、适合特定应用和目标受众的内容成为一个关键挑战。例如,我们可能希望语言模型在与幼儿园孩子互动时使用不同的语言,或在撰写喜剧小品、提供法律支持或总结新闻文章时采用不同的风格。 目前,最成功的LLM范式是训练…

使用python基于DeepLabv3实现对图片进行语义分割

DeepLabv3 介绍 DeepLabv3 是一种先进的语义分割模型,由 Google Research 团队提出。它在 DeepLab 系列模型的基础上进行了改进,旨在提高图像中像素级分类的准确性。以下是 DeepLabv3 的详细介绍: 概述DeepLabv3 是 DeepLab 系列中的第三代…

无人机控制和飞行、路径规划技术分析

无人机控制和飞行、路径规划技术是现代无人机技术的核心组成部分,它们共同决定了无人机的性能和应用范围。以下是对这些技术的详细分析: 一、无人机控制技术 无人机控制技术主要涉及飞行控制系统的设计、传感器数据的处理以及指令的发送与执行。飞行控…

新闻推荐系统开发:Spring Boot实践指南

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统,它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等,非常适…

厂商资源分享网站

新华三(H3C)是一家中国知名的网络设备供应商,提供网络设备、网络解决方案和云计算服务。公司成立于2003年,是华为公司和惠普公司合资的企业,总部位于中国深圳。 华为(Huawei)是一家全球知名的电…

本地运行LLama 3.2的三种方法

大型语言模型(LLMs)已经彻底改变了AI领域,小型模型也在崛起。因此,即使是在旧的PC和智能手机上运行先进的LLMs也成为了可能。为了给大家一个起点,我们将探索三种不同的方法来本地与LLama 3.2进行交互。 先决条件 在我…

【2022工业3D异常检测文献】AST: 基于归一化流的双射性产生不对称学生-教师异常检测方法

Asymmetric Student-Teacher Networks for Industrial Anomaly Detection 1、Background 所谓的学生-教师网络,首先,对教师进行训练,以学习语义嵌入的辅助性训练任务;其次,训练学生以匹配教师的输出。主要目的是让学生…

YOLOv11改进 | Conv篇 | YOLOv11引入SAConv模块

1. SAConv介绍 1.1 摘要: 许多现代物体检测器通过使用三思而后行的机制表现出出色的性能。 在本文中,我们在目标检测的主干设计中探索了这种机制。 在宏观层面,我们提出了递归特征金字塔,它将特征金字塔网络的额外反馈连接合并到自下而上的骨干层中。 在微观层面,我们提出…

LabVIEW提高开发效率技巧----属性节点优化

在LabVIEW开发中,优化代码的效率和性能是非常重要的,尤其是在涉及前面板控件的属性节点时。频繁使用属性节点可能会导致程序执行速度的明显下降,特别是在处理大量数据或高频率操作时。下面详细介绍一些在LabVIEW开发中优化属性节点使用的技巧…