当前位置: 首页 > news >正文

接口幂等性实现方式

优质博文:IT-BLOG-CN

幂等 操作的特点是一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。幂等函数或幂等方法是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。这对于保证系统的一致性和可靠性非常重要。

具体来说,当一个接口被设计为幂等的时候,无论请求被执行多少次,结果都是一样的。这样可以避免由于网络延迟、重试或其他原因导致的重复请求对系统造成的副作用,比如重复创建订单、重复扣款等。实现接口幂等性可以提高系统的可靠性和稳定性,减少不必要的资源消耗和数据错误。同时,对于一些需要保证数据一致性的操作,比如金融交易、库存管理等,实现接口幂等性也是非常重要的。

一、为什么要实现接口幂等性

【1】前端重复提交表单: 在填写一些表格时候,用户填写完成提交,很多时候会因网络波动没有及时对用户做出提交成功响应,致使用户认为没有成功提交,然后一直点提交按钮,这时就会发生重复提交表单请求。
【2】接口超时重复提交: 很多时候HTTP客户端工具都默认开启超时重试的机制,尤其是第三方调用接口时候,为了防止网络波动超时等造成的请求失败,都会添加重试机制,导致一个请求提交多次。
【3】消息进行重复消费: 当使用MQ消息中间件时候,如果发生消息中间件出现错误未及时提交消费信息,导致发生重复消费。
【4】用户恶意进行刷单: 在实现用户投票这种功能时,如果用户针对一个用户进行重复提交投票,这样会导致接口接收到用户重复提交的投票信息,这样会使投票结果与事实严重不符。

二、什么时候做幂等性

请求类型是否需要幂等描述
Get自身满足幂等性Get 方法用于获取资源。其一般不会也不应当对系统资源进行改变,所以是幂等的。
Post自身不满足幂等性Post 方法一般用于创建新的资源。其每次执行都会新增数据,所以不是幂等的。
Put可能满足,可能不满足Put 方法一般用于修改资源。该操作则分情况来判断是不是满足幂等,更新操作中直接根据某个值进行更新,也能保持幂等。不过执行累加操作的更新是非幂等。
Delete可能满足,可能不满足Delete 方法一般用于删除资源。该操作则分情况来判断是不是满足幂等,当根据唯一值进行删除时,删除同一个数据多次执行效果一样。但带查询条件的删除就不一定满足幂等,例如在根据某条件删除一批数据后,这时候新增加了一条数据也满足条件,然后又执行了一次删除,那么将会导致新增加的这条满足条件数据也被删除。

三、如何实现幂等性

客户端

客户端防止重复提交并不是绝对可靠的,可以通过工具略过前端直接访问后端。优点是实现起来比较简单。

按钮只能操作一次

提交后把按钮置灰或loding状态,消除用户因为重复点击而产生的重复记录,比如添加操作,由于点击两次而产生两条记录。

使用Post/Redirect/Get模式

在提交后执行页面重定向,这就是所谓的Post-Redirect—Get(PRG)模式。当用户提交表单后,跳转到一个重定向的信息页面,避免用户按F5刷新导致的重复提交,而且也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退导致同样重复提交的问题。

服务端

数据库唯一主键

利用数据库中主键唯一约束的特性,一般来说唯一主键比较适用于“插入”时的幂等性,其能保证一张表中只能存在一条带该唯一主键的记录。使用数据库唯一主键完成幂等性时需要注意的是,该主键一般来说并不是使用数据库中自增主键,而是使用分布式ID充当主键,这样才能能保证在分布式环境下ID的全局唯一性。

唯一索引

与唯一主键的思想一致,通过唯一性确保插入的幂等性。创建订单时,前端先通过接口获取订单号,再请求后端时带入订单号,订单表中订单号添加唯一索引,如果存在插入相同订单号则直接报错。消费MQ消息时,messageId是唯一的,我们可以新添加一种消费记录表,将messageId作为主键,如果重复消费那么就会存在相同的messageId,插入直接报错。

乐观锁

乐观锁就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制。数据库乐观锁一般只能适用于执行“更新操作”的过程,我们可以提前在对应的数据表中多添加一个字段,充当当前数据的版本标识。每次对该数据库数据执行更新时,都会将该版本标识作为一个条件,值为上次待更新数据的版本标识。

悲观锁

当对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。如下伪代码通过for update添加悲观锁,这里order_id需要有索引,否则会锁整张表。悲观锁影响性能一般不建议。

begin;  --  1.开始事务--  查询订单,判断状态select order_id,status from orders where order_id='1000234' for update ifstatus != 'S'){-- 非订单状态,不能更新为已完成;return ;}--  更新完成update order set status='s' order_no='1000234' 
commit; -- 2.提交事务

状态码

很多业务表,都是有状态的,比如订单表,一般订单有1-订单创建、2-订单支付、3-订单完成、4-取消订单等订单流程,当我们更新订单状态

update orders set status=3 where order_id='1000234' and status=2;

第一次请求时,将“订单支付”状态修改成“订单完成”,sql执行结果的影响行数是1。
第二次重复请求时,同样将“订单支付”状态修改成“订单完成”,但是sql执行结果的影响行数为0。如果是0,那么我们直接可以返回成功了。不需要做接下来的业务操作,以此来保证保证接口的幂等性。

基于分布式锁

分布式锁实现幂等性的逻辑就是,请求过来时,先去尝试获得分布式锁,如果获得成功,就执行业务逻辑,反之获取失败的话,就舍弃请求直接返回成功。其实前面介绍过的悲观锁,本质是使用了数据库的分布式锁,都是将多个操作打包成一个原子操作,保证幂等。但由于数据库分布式锁的性能不太好,我们都是通过RedisZookeeper来实现分布式锁。

客户端 + 服务端

Token机制

针对客户端连续点击或者调用方的超时重试等情况,可以用Token的机制实现防止重复提交。简单的说就是调用方在调用接口的时候先向后端请求一个全局ID(Token),请求的时候携带这个全局ID一起请求(Token最好将其放到Headers中),后端需要对这个Token作为Key,用户信息作为ValueRedis中进行键值内容校验,如果Key存在且Value匹配就执行删除命令,然后正常执行后面的业务逻辑。如果不存在对应的KeyValue不匹配就返回重复执行的错误信息,这样来保证幂等操作。

Token工具类: 接收Token串,加上Key前缀形成Key,再传入value值,执行Lua表达式保证命令执行的原子性,查找对应Key与删除操作。执行完成后验证命令的返回结果,如果结果不为空且非0,则验证成功,否则失败。

public class TockenUtlls {@Autowiredprivate RedisTemplate redisTemplate;// token前缀private static final String TOKEN_PRE = "token_";// 创建 token 并传入 Redispublic String generateToken(String value) { // 通过 UUID 创建 TokenString token = UUID.randomUUID().toString();String key = TOKEN_PRE + token;// 存储 Token 到 Redis 并设置超时时间redisTemplate.opsForValue().set(key, value, 10, TimeUnit.MINUTES);return token;}// 验证 Tokenpublic boolean validToken(String token, String value) {// 设置 LUA 脚本, KEYS[1] 代表 key, KEYS[2] 代表 valueString luaScript = " if redis.call('get', KEYS[1]) == KEYS[2]then return redis.call('get', KEYS[1]) else return 0 end ";RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);// 拼接 Redis KeyString key = TOKEN_PRE + token;// 执行 LUA 脚本Long result = redisTemplate.execute(redisScript, Arrays.asList(key, value));// 根据返回值判断是否匹配成功并删除 Redis 键值对。   结果不为空则表示成功if (result != null && result != 0L) {redisTemplate.opsForValue().getAndDelete(key);return true;}return false;}
}

唯一序号

所谓请求序列号,其实就是每次向服务端请求时候附带一个短时间内唯一不重复的序列号,该序列号可以是一个有序ID,也可以是一个订单号,一般由上游生成,在调用下游服务端接口时附加该序列号和用于认证的ID。当下游服务器收到请求信息后拿取该“序列号”和上游"认证ID"进行组合,形成用于操作RedisKey,然后到Redis中查询是否存在对应的Key的键值对,如果不存在,就以该Key作为Redis的键,以上游关键信息作为存储的值,将该键值对存储到Redis中 ,然后再正常执行对应的业务逻辑即可。

在这里插入图片描述

相关文章:

接口幂等性实现方式

优质博文&#xff1a;IT-BLOG-CN 幂等 操作的特点是一次和多次请求某一个资源对于资源本身应该具有同样的结果&#xff08;网络超时等问题除外&#xff09;。幂等函数或幂等方法是指可以使用相同参数重复执行&#xff0c;并能获得相同结果的函数。这些函数不会影响系统状态&am…...

redis高可用之持久化

目录 一、Redis 高可用的相关知识 1&#xff09;什么是高可用 2&#xff09;Redis的高可用技术 3&#xff09;持久化的功能 4&#xff09;redis持久化的方式 二、RDB持久化 1&#xff09;RDB持久化的触发方式 &#xff08;1&#xff09;手动触发 &#xff08;2&…...

Cocos Creator 3.8 后期效果 Shader 编写(2/2) 进阶篇

前言 在上一篇文章中&#xff0c;麒麟子给大家分享了如何在 Cocos Creator 3.8 中的自定义管线中&#xff0c;添加属于自己的后期效果 Shader。 但基于 BlitScreen 的方案&#xff0c;我们只能编写最简单后效 Shader&#xff0c;如果我们想要支持更多复杂的 Shader&#xff0c…...

【JS自用模板】自动点击选课的操作模板

以激动点击课程为案例复习一下基本前端&#xff0c;容易涉及的问题包括如何提取object类的数字&#xff0c;setTimeout为什么不起作用&#xff1f; 具体思路是&#xff0c;此处会立刻选中符合条件的页面元素打开&#xff0c;然后1小时后会刷新页面&#xff0c;相应地播放页面也…...

TENNECO EDI 项目——X12与XML之间的转换

近期为了帮助广大用户更好地使用 EDI 系统&#xff0c;我们根据以往的项目实施经验&#xff0c;将成熟的 EDI 项目进行开源。用户安装好知行之桥EDI系统之后&#xff0c;只需要下载我们整理好的示例代码&#xff0c;并放置在知行之桥指定的工作区中&#xff0c;即可开始使用。 …...

C++项目:在线五子棋对战(网页版)

项目介绍 本项⽬主要实现⼀个⽹⻚版的五⼦棋对战游戏&#xff0c;其主要⽀持以下核⼼功能&#xff1a; • 用户管理:实现用户注册&#xff0c;用户登录、获取用户信息、用户天梯分数记录、用户比赛场次记录等。 • 匹配对战:实现两个玩家在网页端根据天梯分数匹配游戏对⼿&…...

flutter遇到的小问题记录

flutter-getx的Get.bottomSheet组件改变高度 Get.bottomSheet( isScrollControlled: true,) isScrollControlled: true 就是控制高度 (无语) 截取视频第一针 返回的是本地url 或者Uint8List的数据 String? videoStr await VideoThumbnail.thumbnailFile(video: videoPath,…...

Golang bitset 基本使用

安装&#xff1a; go get github.com/bits-and-blooms/bitset下面代码把fmtx换成fmt就行 //------------基本操作------------//构建一个64bit长度的bitsetb : bitset.New(64)//放入一个数b.Set(10)fmtx.Println("add-10&#xff1a;", b.DumpAsBits()) // 0000000…...

sql 分组讨论,二级分组(非2个字段分组),使用 窗口函数和普通分组实现

1. 二级分组需求 先按照一个字段分组&#xff0c;在按照 第二个字段分组。之后&#xff0c;如果 这个 二级分组中的数据&#xff0c;是 > 1条的。就筛选出来。 比如&#xff1a; 先按照 站点分组&#xff0c;再按照 设备分组&#xff0c; 即&#xff1a;如果站点上配置了…...

业务中如何过滤敏感词

在我们访问网站的时候&#xff0c;如果发现我们发布的内容有色情暴力的东西等等&#xff0c;会屏蔽掉&#xff0c;这种行为就是过滤敏感词。 从技术层面实现起来&#xff0c;其实比较简单&#xff0c;因为我们输入的内容就是一个大型的字符串&#xff0c;我们要调用某些api来判…...

用服务器搭建网站需要做什么

网站建设是一个广义的术语&#xff0c;涵盖了许多不同的技能和学科中所使用的生产和维护的网站。不同领域的网页设计&#xff0c;网页图形设计&#xff0c;界面设计&#xff0c;创作&#xff0c;其中包括标准化的代码和专有软件&#xff0c;用户体验设计和搜索引擎优化。许多人…...

clickhouse 删除操作

OLAP 数据库设计的宗旨在于分析适合一次插入多次查询的业务场景&#xff0c;市面上成熟的 AP 数据库在更新和删除操作上支持的均不是很好&#xff0c;当然 clickhouse 也不例外。但是不友好不代表不支持&#xff0c;本文主要介绍在 clickhouse 中如何实现数据的删除&#xff0c…...

C 语言中,「.」与「->」有什么区别?

使用“.”的话&#xff0c;只需要声明一个结构体。格式是结构体类型名结构体名。然后通过结构体名加上“.”再加上域名&#xff0c;就可以引用结构体的域了。因为结构体的内存是自动分配的&#xff0c;就像使用int a;一样。而使用“->”的话&#xff0c;需要声明一个结构体的…...

github pages 用法详解 发布自己的网站

github pages 基础用法 URL 规则 假设你的 github 帐号为 mygithub&#xff0c;需要发布的仓库名为 myrepo&#xff0c;那么 pages 的 URL 为&#xff1a; https://mygithub.github.io/myrepo 添加内容 用任意编辑器写好&#xff08;或者生成&#xff09;标准的网页内容&a…...

坤简炫酷的JQuery轮播图插件

介绍&#xff1a; 找到了一个炫酷的JQuery轮播图插件&#xff0c;只需要配置三四行代码就可以实现很多二维三维炫酷的切换效果。 视频效果及教程&#xff1a; https://www.bilibili.com/video/BV1Fu4y1d776/ 代码&#xff1a; https://github.com/w-x-x-w/AwesomeWeb 使用…...

C# 条件编译

C# 条件编译 C# 条件编译&#xff1a;根据不同的需求&#xff0c;编译生成不同的程序版本&#xff0c;条件编译是一种编译预处理命令&#xff0c;它是在编译代码之前对源代码进行处理。它可以根据条件&#xff0c;决定是否编译某段代码 条件编译的三种形式&#xff1a; 第一种…...

IntelliJ IDEA如何重新弹出git身份验证窗口

1、点击File菜单—>点击Settings—>点击Appearance & Behavior—>点击System Settings—>点击Passwords—>选中Do not save, forget passwords after restart—>点击Apply—>点击OK&#xff0c;如下所示&#xff1a; 2、重启IntelliJ IDEA—>通过g…...

【雕爷学编程】Arduino动手做(200)---WS2812B幻彩LED灯带4

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…...

【雕爷学编程】Arduino动手做(201)---DFRobot 行空板03

37款传感器与模块的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&#x…...

Spring中Bean的“一生”(生命周期)

文章目录 一、图解二、文字解析总结 一、图解 >注&#xff1a;处于同一行的执行顺序是从左往右 二、文字解析 SpringBean的生命周期总体分为四个阶段&#xff1a;实例化>属性注入>初始化>销毁 Step1 实例化Bean&#xff1a;根据配置文件中Bean的定义&#xff0c;…...

C++:std::is_convertible

C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器

一.自适应梯度算法Adagrad概述 Adagrad&#xff08;Adaptive Gradient Algorithm&#xff09;是一种自适应学习率的优化算法&#xff0c;由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率&#xff0c;适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南

精益数据分析&#xff08;97/126&#xff09;&#xff1a;邮件营销与用户参与度的关键指标优化指南 在数字化营销时代&#xff0c;邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天&#xff0c;我们将深入解析邮件打开率、网站可用性、页面参与时…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

Java + Spring Boot + Mybatis 实现批量插入

在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法&#xff1a;使用 MyBatis 的 <foreach> 标签和批处理模式&#xff08;ExecutorType.BATCH&#xff09;。 方法一&#xff1a;使用 XML 的 <foreach> 标签&#xff…...